mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-05 00:36:57 +00:00
5dcdfd2600
The major contributors provided their consent: DrMcCoy, Strangerke, sdelamarre, sev. The goal is to allow the re-release of this code under Switch, which is incompatible with pure GPL
2257 lines
53 KiB
C++
2257 lines
53 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 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* This file is dual-licensed.
|
|
* In addition to the GPLv3 license mentioned above, this code is also
|
|
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
|
|
* full text of the license.
|
|
*
|
|
*/
|
|
|
|
#include "common/str.h"
|
|
|
|
#include "gob/hotspots.h"
|
|
#include "gob/global.h"
|
|
#include "gob/draw.h"
|
|
#include "gob/game.h"
|
|
#include "gob/script.h"
|
|
#include "gob/inter.h"
|
|
|
|
namespace Gob {
|
|
|
|
Hotspots::Hotspot::Hotspot() {
|
|
clear();
|
|
}
|
|
|
|
Hotspots::Hotspot::Hotspot(uint16 i,
|
|
uint16 l, uint16 t, uint16 r, uint16 b, uint16 f, uint16 k,
|
|
uint16 enter, uint16 leave, uint16 pos) {
|
|
|
|
id = i;
|
|
left = l;
|
|
top = t;
|
|
right = r;
|
|
bottom = b;
|
|
flags = f;
|
|
key = k;
|
|
funcEnter = enter;
|
|
funcLeave = leave;
|
|
funcPos = pos;
|
|
script = nullptr;
|
|
}
|
|
|
|
void Hotspots::Hotspot::clear() {
|
|
id = 0;
|
|
left = 0xFFFF;
|
|
top = 0;
|
|
right = 0;
|
|
bottom = 0;
|
|
flags = 0;
|
|
key = 0;
|
|
funcEnter = 0;
|
|
funcLeave = 0;
|
|
funcPos = 0;
|
|
script = nullptr;
|
|
}
|
|
|
|
Hotspots::Type Hotspots::Hotspot::getType() const {
|
|
return (Type) (flags & 0xF);
|
|
}
|
|
|
|
MouseButtons Hotspots::Hotspot::getButton() const {
|
|
uint8 buttonBits = ((flags & 0x70) >> 4);
|
|
|
|
if (buttonBits == 0)
|
|
return kMouseButtonsLeft;
|
|
if (buttonBits == 1)
|
|
return kMouseButtonsRight;
|
|
if (buttonBits == 2)
|
|
return kMouseButtonsAny;
|
|
|
|
return kMouseButtonsNone;
|
|
}
|
|
|
|
uint16 Hotspots::Hotspot::getWindow() const {
|
|
return (flags & 0x0F00);
|
|
}
|
|
|
|
uint8 Hotspots::Hotspot::getCursor() const {
|
|
return (flags & 0xF000) >> 12;
|
|
}
|
|
|
|
uint8 Hotspots::Hotspot::getState(uint16 id) {
|
|
return (id & 0xF000) >> 12;
|
|
}
|
|
|
|
uint8 Hotspots::Hotspot::getState() const {
|
|
return getState(id);
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isEnd() const {
|
|
return (left == 0xFFFF);
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isInput() const {
|
|
if (getType() < kTypeInput1NoLeave)
|
|
return false;
|
|
|
|
if (getType() > kTypeInputFloatLeave)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isActiveInput() const {
|
|
if (isEnd())
|
|
return false;
|
|
|
|
if (!isFilledEnabled())
|
|
return false;
|
|
|
|
if (!isInput())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isInputLeave() const {
|
|
if (!isInput())
|
|
return false;
|
|
|
|
if (!(getType() & 1))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isFilled() const {
|
|
return getState() & kStateFilled;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isFilledEnabled() const {
|
|
return (getState() & kStateFilledDisabled) == kStateFilled;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isFilledNew() const {
|
|
return getState() == kStateFilled;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isDisabled() const {
|
|
return getState() & kStateDisabled;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::isIn(uint16 x, uint16 y) const {
|
|
// FIXME: the cast to int16 is a hack, to fix handling of Gob2 problems related to
|
|
// hotspots with negative offset (to temporary disable them).
|
|
if ((int16) x < (int16) left)
|
|
return false;
|
|
if ((int16) x > (int16) right)
|
|
return false;
|
|
if ((int16) y < (int16) top)
|
|
return false;
|
|
if ((int16) y > (int16) bottom)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Hotspots::Hotspot::buttonMatch(MouseButtons button) const {
|
|
MouseButtons myButton = getButton();
|
|
|
|
if (myButton == kMouseButtonsAny)
|
|
// Any button allowed
|
|
return true;
|
|
|
|
if (myButton == kMouseButtonsNone)
|
|
// No button allowed
|
|
return false;
|
|
|
|
if (myButton == button)
|
|
// Exact match
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void Hotspots::Hotspot::disable() {
|
|
id |= (kStateDisabled << 12);
|
|
}
|
|
|
|
void Hotspots::Hotspot::enable() {
|
|
id &= ~(kStateDisabled << 12);
|
|
}
|
|
|
|
|
|
Hotspots::Hotspots(GobEngine *vm) : _vm(vm) {
|
|
_hotspots = new Hotspot[kHotspotCount];
|
|
|
|
_shouldPush = false;
|
|
|
|
_currentKey = 0;
|
|
_currentIndex = 0;
|
|
_currentId = 0;
|
|
_currentX = 0;
|
|
_currentY = 0;
|
|
}
|
|
|
|
Hotspots::~Hotspots() {
|
|
delete[] _hotspots;
|
|
|
|
// Pop the whole stack and free each element's memory
|
|
while (!_stack.empty()) {
|
|
|
|
StackEntry backup = _stack.pop();
|
|
|
|
delete[] backup.hotspots;
|
|
}
|
|
}
|
|
|
|
void Hotspots::clear() {
|
|
_currentKey = 0;
|
|
|
|
for (int i = 0; i < kHotspotCount; i++)
|
|
_hotspots[i].clear();
|
|
}
|
|
|
|
uint16 Hotspots::add(uint16 id,
|
|
uint16 left, uint16 top, uint16 right, uint16 bottom,
|
|
uint16 flags, uint16 key,
|
|
uint16 funcEnter, uint16 funcLeave, uint16 funcPos) {
|
|
|
|
Hotspot hotspot(id, left, top, right, bottom,
|
|
flags, key, funcEnter, funcLeave, funcPos);
|
|
|
|
return add(hotspot);
|
|
}
|
|
|
|
uint16 Hotspots::add(const Hotspot &hotspot) {
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
Hotspot &spot = _hotspots[i];
|
|
|
|
// free space => add same id => update
|
|
if (! (spot.isEnd() || (spot.id == hotspot.id)))
|
|
continue;
|
|
|
|
// When updating, keep disabled state intact
|
|
uint16 id = hotspot.id;
|
|
if ((spot.id & ~(kStateDisabled << 12)) ==
|
|
(hotspot.id & ~(kStateDisabled << 12)))
|
|
id = spot.id;
|
|
|
|
// Set
|
|
spot = hotspot;
|
|
spot.id = id;
|
|
|
|
// Remember the current script
|
|
spot.script = _vm->_game->_script;
|
|
|
|
debugC(1, kDebugHotspots, "Adding hotspot %03d: Coord:%3d+%3d+%3d+%3d - id:%04X, key:%04X, flag:%04X - fcts:%5d, %5d, %5d",
|
|
i, spot.left, spot.top, spot.right, spot.bottom,
|
|
spot.id, spot.key, spot.flags, spot.funcEnter, spot.funcLeave, spot.funcPos);
|
|
|
|
return i;
|
|
}
|
|
|
|
error("Hotspots::add(): Hotspot array full");
|
|
return 0xFFFF; // for compilers that don't support NORETURN
|
|
}
|
|
|
|
void Hotspots::remove(uint16 id) {
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
if (_hotspots[i].id == id) {
|
|
debugC(1, kDebugHotspots, "Removing hotspot %d: %X", i, id);
|
|
_hotspots[i].clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Hotspots::removeState(uint8 state) {
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
Hotspot &spot = _hotspots[i];
|
|
|
|
if (spot.getState() == state) {
|
|
debugC(1, kDebugHotspots, "Removing hotspot %d: %X (by state %X)", i, spot.id, state);
|
|
spot.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Hotspots::recalculate(bool force) {
|
|
debugC(5, kDebugHotspots, "Recalculating hotspots");
|
|
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
Hotspot &spot = _hotspots[i];
|
|
|
|
if (!force && ((spot.flags & 0x80) != 0))
|
|
// Not forcing a special hotspot
|
|
continue;
|
|
|
|
if (spot.funcPos == 0)
|
|
// Simple coordinates don't need update
|
|
continue;
|
|
|
|
// Setting the needed script
|
|
Script *curScript = _vm->_game->_script;
|
|
|
|
_vm->_game->_script = spot.script;
|
|
if (!_vm->_game->_script)
|
|
_vm->_game->_script = curScript;
|
|
|
|
// Calling the function that contains the positions
|
|
_vm->_game->_script->call(spot.funcPos);
|
|
|
|
// Calculate positions
|
|
int16 left = _vm->_game->_script->readValExpr();
|
|
int16 top = _vm->_game->_script->readValExpr();
|
|
int16 width = _vm->_game->_script->readValExpr();
|
|
int16 height = _vm->_game->_script->readValExpr();
|
|
|
|
// Re-read the flags too, if applicable
|
|
uint16 flags = 0;
|
|
if (spot.getState() == (kStateFilled | kStateType2))
|
|
flags = _vm->_game->_script->readValExpr();
|
|
|
|
// Apply backDelta, if needed
|
|
if ((_vm->_draw->_renderFlags & RENDERFLAG_CAPTUREPOP) && (left != -1)) {
|
|
left += _vm->_draw->_backDeltaX;
|
|
top += _vm->_draw->_backDeltaY;
|
|
}
|
|
|
|
if (_vm->_draw->_needAdjust != 2 && _vm->_draw->_needAdjust != 10) {
|
|
_vm->_draw->adjustCoords(0, &left, &top);
|
|
if ((spot.flags & 15) < 3)
|
|
_vm->_draw->adjustCoords(2, &width, &height);
|
|
else {
|
|
height &= 0xFFFFFFFE;
|
|
_vm->_draw->adjustCoords(2, nullptr, &height);
|
|
}
|
|
}
|
|
|
|
// Clamping
|
|
if (left < 0) {
|
|
width += left;
|
|
left = 0;
|
|
}
|
|
if (top < 0) {
|
|
height += top;
|
|
top = 0;
|
|
}
|
|
|
|
// Set the updated position
|
|
spot.left = left;
|
|
spot.top = top;
|
|
spot.right = left + width - 1;
|
|
spot.bottom = top + height - 1;
|
|
|
|
if (spot.getState() == (kStateFilled | kStateType2))
|
|
spot.flags = flags;
|
|
|
|
// Return
|
|
_vm->_game->_script->pop();
|
|
|
|
_vm->_game->_script = curScript;
|
|
}
|
|
}
|
|
|
|
void Hotspots::push(uint8 all, bool force) {
|
|
debugC(1, kDebugHotspots, "Pushing hotspots (%d, %d)", all, force);
|
|
|
|
// Should we push at all?
|
|
if (!_shouldPush && !force)
|
|
return;
|
|
|
|
// Count the hotspots
|
|
uint32 size = 0;
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
Hotspot &spot = _hotspots[i];
|
|
|
|
// Save all of them
|
|
if ( (all == 1) ||
|
|
// Don't save the global ones
|
|
((all == 0) && (spot.id >= 20)) ||
|
|
// Only save disabled ones
|
|
((all == 2) && ((spot.getState() == (kStateFilledDisabled | kStateType1)) ||
|
|
(spot.getState() == (kStateDisabled)) ||
|
|
(spot.getState() == (kStateFilledDisabled | kStateType2))))) {
|
|
size++;
|
|
}
|
|
|
|
}
|
|
|
|
StackEntry backup;
|
|
|
|
backup.shouldPush = _shouldPush;
|
|
backup.size = size;
|
|
backup.key = _currentKey;
|
|
backup.id = _currentId;
|
|
backup.index = _currentIndex;
|
|
backup.x = _currentX;
|
|
backup.y = _currentY;
|
|
|
|
backup.hotspots = new Hotspot[size];
|
|
|
|
// Copy the hotspots
|
|
Hotspot *destPtr = backup.hotspots;
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
Hotspot &spot = _hotspots[i];
|
|
|
|
// Save all of them
|
|
if ( (all == 1) ||
|
|
// Don't save the global ones
|
|
((all == 0) && (spot.id >= 20)) ||
|
|
// Only save disabled ones
|
|
((all == 2) && ((spot.getState() == (kStateFilledDisabled | kStateType1)) ||
|
|
(spot.getState() == (kStateDisabled)) ||
|
|
(spot.getState() == (kStateFilledDisabled | kStateType2))))) {
|
|
|
|
memcpy(destPtr, &spot, sizeof(Hotspot));
|
|
destPtr++;
|
|
|
|
spot.clear();
|
|
}
|
|
|
|
}
|
|
|
|
// Reset current state
|
|
_shouldPush = false;
|
|
_currentKey = 0;
|
|
_currentId = 0;
|
|
_currentIndex = 0;
|
|
_currentX = 0;
|
|
_currentY = 0;
|
|
|
|
_stack.push(backup);
|
|
}
|
|
|
|
void Hotspots::pop() {
|
|
debugC(1, kDebugHotspots, "Popping hotspots");
|
|
|
|
assert(!_stack.empty());
|
|
|
|
StackEntry backup = _stack.pop();
|
|
|
|
// Find the end of the filled hotspot space
|
|
int i;
|
|
Hotspot *destPtr = _hotspots;
|
|
for (i = 0; i < kHotspotCount; i++, destPtr++) {
|
|
if (destPtr->isEnd())
|
|
break;
|
|
}
|
|
|
|
if (((uint32) (kHotspotCount - i)) < backup.size)
|
|
error("Hotspots::pop(): Not enough free space in the current Hotspot "
|
|
"array to pop %d elements (got %d)", backup.size, kHotspotCount - i);
|
|
|
|
// Copy
|
|
memcpy(destPtr, backup.hotspots, backup.size * sizeof(Hotspot));
|
|
|
|
_shouldPush = backup.shouldPush;
|
|
_currentKey = backup.key;
|
|
_currentId = backup.id;
|
|
_currentIndex = backup.index;
|
|
_currentX = backup.x;
|
|
_currentY = backup.y;
|
|
|
|
delete[] backup.hotspots;
|
|
}
|
|
|
|
bool Hotspots::isValid(uint16 key, uint16 id, uint16 index) const {
|
|
if (index >= kHotspotCount)
|
|
return false;
|
|
|
|
if (key == 0)
|
|
return false;
|
|
|
|
if (!(Hotspot::getState(id) & kStateFilled))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Hotspots::call(uint16 offset) {
|
|
debugC(4, kDebugHotspots, "Calling hotspot function %d", offset);
|
|
|
|
_vm->_game->_script->call(offset);
|
|
|
|
_shouldPush = true;
|
|
|
|
Common::Stack<StackEntry>::size_type stackSize = _stack.size();
|
|
|
|
_vm->_inter->funcBlock(0);
|
|
|
|
while (stackSize != _stack.size())
|
|
pop();
|
|
|
|
_shouldPush = false;
|
|
|
|
_vm->_game->_script->pop();
|
|
|
|
recalculate(false);
|
|
}
|
|
|
|
void Hotspots::enter(uint16 index) {
|
|
debugC(2, kDebugHotspots, "Entering hotspot %d", index);
|
|
|
|
if (index >= kHotspotCount) {
|
|
warning("Hotspots::enter(): Index %d out of range", index);
|
|
return;
|
|
}
|
|
|
|
Hotspot &spot = _hotspots[index];
|
|
|
|
// If requested, write the ID into a variable
|
|
if ((spot.getState() == (kStateFilled | kStateType1)) ||
|
|
(spot.getState() == (kStateFilled | kStateType2)))
|
|
WRITE_VAR(17, -(spot.id & 0x0FFF));
|
|
|
|
_currentX = _vm->_global->_inter_mouseX;
|
|
_currentY = _vm->_global->_inter_mouseY;
|
|
|
|
if (spot.funcEnter != 0)
|
|
call(spot.funcEnter);
|
|
}
|
|
|
|
void Hotspots::leave(uint16 index) {
|
|
debugC(2, kDebugHotspots, "Leaving hotspot %d", index);
|
|
|
|
if (index >= kHotspotCount) {
|
|
warning("Hotspots::leave(): Index %d out of range", index);
|
|
return;
|
|
}
|
|
|
|
Hotspot &spot = _hotspots[index];
|
|
|
|
// If requested, write the ID into a variable
|
|
if ((spot.getState() == (kStateFilled | kStateType1)) ||
|
|
(spot.getState() == (kStateFilled | kStateType2)))
|
|
WRITE_VAR(17, spot.id & 0x0FFF);
|
|
|
|
if (spot.funcLeave != 0)
|
|
call(spot.funcLeave);
|
|
}
|
|
|
|
int16 Hotspots::windowCursor(int16 &dx, int16 &dy) const {
|
|
if (!(_vm->_draw->_renderFlags & RENDERFLAG_HASWINDOWS))
|
|
return 0;
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
if (_vm->_draw->_fascinWin[i].id == -1)
|
|
// No such windows
|
|
continue;
|
|
|
|
const int left = _vm->_draw->_fascinWin[i].left;
|
|
const int top = _vm->_draw->_fascinWin[i].top;
|
|
const int right = _vm->_draw->_fascinWin[i].left + _vm->_draw->_fascinWin[i].width - 1;
|
|
const int bottom = _vm->_draw->_fascinWin[i].top + _vm->_draw->_fascinWin[i].height - 1;
|
|
|
|
if ((_vm->_global->_inter_mouseX < left) || (_vm->_global->_inter_mouseX > right) ||
|
|
(_vm->_global->_inter_mouseY < top ) || (_vm->_global->_inter_mouseY > bottom))
|
|
// We're not inside that window
|
|
continue;
|
|
|
|
if (_vm->_draw->_fascinWin[i].id != (_vm->_draw->_winCount - 1))
|
|
// Only consider the top-most window
|
|
continue;
|
|
|
|
dx = _vm->_draw->_fascinWin[i].left;
|
|
dy = _vm->_draw->_fascinWin[i].top;
|
|
|
|
if ((_vm->_global->_inter_mouseX < (left + 12)) && (_vm->_global->_inter_mouseY < (top + 12)) &&
|
|
(VAR((_vm->_draw->_winVarArrayStatus / 4) + i) & 2))
|
|
// Cursor on 'Close Window'
|
|
return 5;
|
|
|
|
if ((_vm->_global->_inter_mouseX > (right - 12)) & (_vm->_global->_inter_mouseY < (top + 12)) &&
|
|
(VAR((_vm->_draw->_winVarArrayStatus / 4) + i) & 4))
|
|
// Cursor on 'Move Window'
|
|
return 6;
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint16 Hotspots::checkMouse(Type type, uint16 &id, uint16 &index) const {
|
|
id = 0;
|
|
index = 0;
|
|
|
|
int16 dx = 0;
|
|
int16 dy = 0;
|
|
int16 winId = _vm->_draw->getWinFromCoord(dx, dy);
|
|
|
|
if (winId < 0) {
|
|
winId = 0;
|
|
dx = 0;
|
|
dy = 0;
|
|
} else
|
|
winId *= 256;
|
|
|
|
if (type == kTypeMove) {
|
|
// Check where the mouse was moved to
|
|
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (spot.isDisabled())
|
|
// Only consider enabled hotspots
|
|
continue;
|
|
|
|
if (spot.getType() > kTypeMove)
|
|
// Only consider click and move hotspots
|
|
continue;
|
|
|
|
if (spot.getWindow() != winId)
|
|
// Only check the current window
|
|
continue;
|
|
|
|
if (!spot.isIn(_vm->_global->_inter_mouseX - dx, _vm->_global->_inter_mouseY - dy))
|
|
// If we're not in it, ignore it
|
|
continue;
|
|
|
|
id = spot.id;
|
|
index = i;
|
|
|
|
return spot.key;
|
|
}
|
|
|
|
return 0;
|
|
|
|
} else if (type == kTypeClick) {
|
|
// Check if something was clicked
|
|
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (spot.isDisabled())
|
|
// Only consider enabled hotspots
|
|
continue;
|
|
|
|
if (spot.getWindow() != winId)
|
|
// Only check the active window
|
|
continue;
|
|
|
|
if (spot.getType() < kTypeMove)
|
|
// Only consider hotspots that can be clicked
|
|
continue;
|
|
|
|
if (!spot.isIn(_vm->_global->_inter_mouseX - dx, _vm->_global->_inter_mouseY - dy))
|
|
// If we're not in it, ignore it
|
|
continue;
|
|
|
|
if (!spot.buttonMatch(_vm->_game->_mouseButtons))
|
|
// Don't follow hotspots with button requirements we don't meet
|
|
continue;
|
|
|
|
id = spot.id;
|
|
index = i;
|
|
|
|
if ((spot.getType() == kTypeMove) || (spot.getType() == kTypeClick))
|
|
// It's a move or click => return the key
|
|
return spot.key;
|
|
|
|
// Otherwise, the key has a different meaning, so ignore it
|
|
return 0;
|
|
}
|
|
|
|
if (_vm->_game->_mouseButtons != kMouseButtonsLeft)
|
|
// Let the right mouse button act as an escape key
|
|
return kKeyEscape;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool Hotspots::checkHotspotChanged() {
|
|
uint16 key, id, index;
|
|
|
|
// Get the current hotspot
|
|
key = checkMouse(kTypeMove, id, index);
|
|
|
|
uint16 mouseX = _vm->_global->_inter_mouseX;
|
|
uint16 mouseY = _vm->_global->_inter_mouseY;
|
|
|
|
if (key == _currentKey) {
|
|
// Still the same hotspot, just update the mouse position
|
|
|
|
_currentX = mouseX;
|
|
_currentY = mouseY;
|
|
return false;
|
|
}
|
|
|
|
// In Geisha, no move hotspot changes should occur when
|
|
// we didn't actually move the mouse
|
|
if (_vm->getGameType() == kGameTypeGeisha)
|
|
if ((mouseX == _currentX) && (mouseY == _currentY))
|
|
return false;
|
|
|
|
// Leave the old area
|
|
if (isValid(_currentKey, _currentId,_currentIndex))
|
|
leave(_currentIndex);
|
|
|
|
_currentKey = key;
|
|
_currentId = id;
|
|
_currentIndex = index;
|
|
_currentX = mouseX;
|
|
_currentY = mouseY;
|
|
|
|
// Enter the new one
|
|
if (isValid(key, id, index))
|
|
enter(index);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint16 Hotspots::check(uint8 handleMouse, int16 delay, uint16 &id, uint16 &index) {
|
|
if (delay >= -1) {
|
|
_currentKey = 0;
|
|
_currentId = 0;
|
|
_currentIndex = 0;
|
|
}
|
|
|
|
id = 0;
|
|
index = 0;
|
|
|
|
if (handleMouse) {
|
|
if ((_vm->_draw->_cursorIndex == -1) && (_currentKey == 0)) {
|
|
// Last know state: No hotspot hit. Look if that changed
|
|
|
|
_currentKey = checkMouse(kTypeMove, _currentId, _currentIndex);
|
|
|
|
if (isValid(_currentKey, _currentId, _currentIndex))
|
|
enter(_currentIndex);
|
|
}
|
|
_vm->_draw->animateCursor(-1);
|
|
}
|
|
|
|
uint32 startTime = _vm->_util->getTimeKey();
|
|
|
|
// Update display
|
|
_vm->_draw->blitInvalidated();
|
|
_vm->_video->waitRetrace();
|
|
|
|
uint16 key = 0;
|
|
while (key == 0) {
|
|
|
|
if (_vm->_inter->_terminate || _vm->shouldQuit()) {
|
|
if (handleMouse)
|
|
_vm->_draw->blitCursor();
|
|
return 0;
|
|
}
|
|
|
|
// Anything changed?
|
|
checkHotspotChanged();
|
|
|
|
// Update display
|
|
if (!_vm->_draw->_noInvalidated) {
|
|
if (handleMouse)
|
|
_vm->_draw->animateCursor(-1);
|
|
else
|
|
_vm->_draw->blitInvalidated();
|
|
_vm->_video->waitRetrace();
|
|
}
|
|
|
|
if (handleMouse)
|
|
_vm->_game->evaluateScroll();
|
|
|
|
// Update keyboard and mouse state
|
|
key = _vm->_game->checkKeys(&_vm->_global->_inter_mouseX,
|
|
&_vm->_global->_inter_mouseY, &_vm->_game->_mouseButtons, handleMouse);
|
|
|
|
if (!handleMouse && (_vm->_game->_mouseButtons != kMouseButtonsNone)) {
|
|
// We don't want any mouse input but got one => Wait till it went away
|
|
|
|
_vm->_util->waitMouseRelease(0);
|
|
key = 3;
|
|
}
|
|
|
|
if (key != 0) {
|
|
// Got a key press
|
|
|
|
if (handleMouse & 1)
|
|
_vm->_draw->blitCursor();
|
|
|
|
id = 0;
|
|
index = 0;
|
|
|
|
// Leave the current hotspot
|
|
if (isValid(_currentKey, _currentId, _currentIndex))
|
|
leave(_currentIndex);
|
|
|
|
_currentKey = 0;
|
|
break;
|
|
}
|
|
|
|
if (handleMouse) {
|
|
|
|
if (_vm->_game->_mouseButtons != kMouseButtonsNone) {
|
|
// Mouse button pressed
|
|
int i = _vm->_draw->handleCurWin();
|
|
|
|
if (!i) {
|
|
_vm->_draw->animateCursor(2);
|
|
if (delay > 0) {
|
|
// If a delay was requested, wait the specified time
|
|
_vm->_util->delay(delay);
|
|
} else if (handleMouse & 1)
|
|
_vm->_util->waitMouseRelease(1);
|
|
|
|
_vm->_draw->animateCursor(-1);
|
|
|
|
// Which region was clicked?
|
|
key = checkMouse(kTypeClick, id, index);
|
|
|
|
if ((key != 0) || (id != 0)) {
|
|
// Got a valid region
|
|
|
|
if ( (handleMouse & 1) &&
|
|
((delay <= 0) || (_vm->_game->_mouseButtons == kMouseButtonsNone)))
|
|
_vm->_draw->blitCursor();
|
|
|
|
if ((_currentKey != 0) &&
|
|
(key != _currentKey) &&
|
|
(_vm->getGameType() != kGameTypeFascination) &&
|
|
(_vm->getGameType() != kGameTypeGeisha))
|
|
// If the hotspot changed, leave the old one
|
|
// Code not present in Fascination executables
|
|
leave(_currentIndex);
|
|
|
|
_currentKey = 0;
|
|
break;
|
|
}
|
|
|
|
if (handleMouse & 4)
|
|
// Nothing further than one simple check was requested => return
|
|
return 0;
|
|
|
|
// Leave the current area
|
|
if (_currentKey != 0)
|
|
leave(_currentIndex);
|
|
|
|
// No click, but do we have a move event? If so, enter that hotspot
|
|
_currentKey = checkMouse(kTypeMove, _currentId, _currentIndex);
|
|
if (isValid(_currentKey, _currentId, _currentIndex))
|
|
enter(_currentIndex);
|
|
} else {
|
|
WRITE_VAR(16, (int32)i);
|
|
id = 0;
|
|
index = 0;
|
|
return 0;
|
|
}
|
|
} else
|
|
// No mouse button pressed, check whether the position changed at least
|
|
checkHotspotChanged();
|
|
}
|
|
|
|
if ((delay == -2) && (key == 0) &&
|
|
(_vm->_game->_mouseButtons == kMouseButtonsNone)) {
|
|
// Nothing found and no further handling requested. Return.
|
|
|
|
id = 0;
|
|
index = 0;
|
|
break;
|
|
}
|
|
|
|
if (handleMouse)
|
|
_vm->_draw->animateCursor(-1);
|
|
|
|
if ((delay < 0) && (key == 0) &&
|
|
(_vm->_game->_mouseButtons == kMouseButtonsNone)) {
|
|
|
|
// Look if we've maybe reached the timeout
|
|
|
|
uint32 curTime = _vm->_util->getTimeKey();
|
|
if ((curTime + delay) > startTime) {
|
|
// If so, return
|
|
|
|
id = 0;
|
|
index = 0;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// Sleep for a short amount of time
|
|
_vm->_util->delay(10);
|
|
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
uint16 Hotspots::check(uint8 handleMouse, int16 delay) {
|
|
uint16 id, index;
|
|
|
|
// Check and ignore the id and index
|
|
return Hotspots::check(handleMouse, delay, id, index);
|
|
}
|
|
|
|
uint16 Hotspots::updateInput(uint16 xPos, uint16 yPos, uint16 width, uint16 height,
|
|
uint16 backColor, uint16 frontColor, char *str, uint16 fontIndex,
|
|
Type type, int16 &duration, uint16 &id, uint16 &index) {
|
|
|
|
if ((fontIndex >= Draw::kFontCount) || !_vm->_draw->_fonts[fontIndex]) {
|
|
warning("Hotspots::updateInput(): Invalid font specified: %d", fontIndex);
|
|
return 0;
|
|
}
|
|
|
|
// Check if we need to consider mouse events
|
|
bool handleMouse = false;
|
|
if ( (_vm->_game->_handleMouse != 0) &&
|
|
((_vm->_global->_useMouse != 0) || (_vm->_game->_forceHandleMouse != 0)))
|
|
handleMouse = true;
|
|
|
|
const Font &font = *_vm->_draw->_fonts[fontIndex];
|
|
|
|
// Current position in the string, preset to the end
|
|
uint32 pos = strlen(str);
|
|
/* Size of input field in characters.
|
|
* If the font is not monospaced, we can't know that */
|
|
uint32 editSize = font.isMonospaced() ? (width / font.getCharWidth()) : 0;
|
|
|
|
uint16 key = 0;
|
|
char tempStr[256];
|
|
|
|
while (1) {
|
|
// If we the edit field has enough space, add a space for the new character
|
|
Common::strlcpy(tempStr, str, 255);
|
|
Common::strcat_s(tempStr, " ");
|
|
if ((editSize != 0) && strlen(tempStr) > editSize)
|
|
Common::strlcpy(tempStr, str, 256);
|
|
|
|
// Clear input area
|
|
fillRect(xPos, yPos,
|
|
font.isMonospaced() ? (editSize * font.getCharWidth()) : width, height,
|
|
backColor);
|
|
|
|
// Print the current string, vertically centered
|
|
printText(xPos, yPos + (height - font.getCharHeight()) / 2,
|
|
tempStr, fontIndex, frontColor);
|
|
|
|
// If we've reached the end of the input field, set the cursor to the last character
|
|
if ((editSize != 0) && (pos == editSize))
|
|
pos--;
|
|
|
|
// The character under the cursor
|
|
char curSym = tempStr[pos];
|
|
|
|
if (_vm->_inter->_variables)
|
|
WRITE_VAR(56, pos);
|
|
|
|
bool first = true;
|
|
while (1) {
|
|
tempStr[0] = curSym;
|
|
tempStr[1] = 0;
|
|
|
|
// Draw cursor
|
|
uint16 cursorX, cursorY, cursorWidth, cursorHeight;
|
|
getTextCursorPos(font, str, pos, xPos, yPos, width, height,
|
|
cursorX, cursorY, cursorWidth, cursorHeight);
|
|
fillRect(cursorX, cursorY, cursorWidth, cursorHeight, frontColor);
|
|
|
|
if (first) {
|
|
// The first time, purge old information too
|
|
key = check(handleMouse, -1, id, index);
|
|
|
|
if (key == 0)
|
|
// We didn't catch any input, let's try again with a real timeout
|
|
key = check(handleMouse, -300, id, index);
|
|
|
|
first = false;
|
|
} else
|
|
// Try to catch a character
|
|
key = check(handleMouse, -300, id, index);
|
|
|
|
tempStr[0] = curSym;
|
|
tempStr[1] = 0;
|
|
|
|
// Clear cursor
|
|
getTextCursorPos(font, str, pos, xPos, yPos, width, height,
|
|
cursorX, cursorY, cursorWidth, cursorHeight);
|
|
fillRect(cursorX, cursorY, cursorWidth, cursorHeight, backColor);
|
|
|
|
// Print the current string, vertically centered
|
|
printText(cursorX, yPos + (height - font.getCharHeight()) / 2,
|
|
tempStr, fontIndex, frontColor);
|
|
|
|
if ((key != 0) || (id != 0))
|
|
// We did get a key, stop looking
|
|
break;
|
|
|
|
// Try again
|
|
key = check(handleMouse, -300, id, index);
|
|
|
|
if ((key != 0) || (id != 0) ||
|
|
_vm->_inter->_terminate || _vm->shouldQuit())
|
|
// We did get a key, stop looking
|
|
break;
|
|
|
|
if (duration > 0) {
|
|
// Look if we reached the time limit
|
|
duration -= 600;
|
|
if (duration <= 1) {
|
|
// If so, abort
|
|
key = 0;
|
|
id = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((key == 0) || (id != 0) ||
|
|
_vm->_inter->_terminate || _vm->shouldQuit())
|
|
// Got no key, or a region ID instead, return
|
|
return 0;
|
|
|
|
switch (key) {
|
|
case kKeyRight:
|
|
// If possible, move the cursor right
|
|
if (((editSize != 0) && ((pos > strlen(str)) || (pos > (editSize - 1)))) ||
|
|
((editSize == 0) && (pos > strlen(str)))) {
|
|
pos++;
|
|
continue;
|
|
}
|
|
// Continue downwards instead
|
|
return kKeyDown;
|
|
|
|
case kKeyLeft:
|
|
// If possible, move the cursor left
|
|
if (pos > 0) {
|
|
pos--;
|
|
continue;
|
|
}
|
|
// Continue upwards instead
|
|
return kKeyUp;
|
|
|
|
case kKeyBackspace:
|
|
if (pos > 0) {
|
|
// Delete the character to the left
|
|
_vm->_util->cutFromStr(str, pos - 1, 1);
|
|
pos--;
|
|
} else {
|
|
if (pos < strlen(str))
|
|
// Delete the character to the right
|
|
_vm->_util->cutFromStr(str, pos, 1);
|
|
}
|
|
continue;
|
|
|
|
case kKeyDelete:
|
|
if (pos >= strlen(str))
|
|
continue;
|
|
|
|
// Delete the character to the right
|
|
_vm->_util->cutFromStr(str, pos, 1);
|
|
continue;
|
|
|
|
case kKeyReturn:
|
|
case kKeyF1:
|
|
case kKeyF2:
|
|
case kKeyF3:
|
|
case kKeyF4:
|
|
case kKeyF5:
|
|
case kKeyF6:
|
|
case kKeyF7:
|
|
case kKeyF8:
|
|
case kKeyF9:
|
|
case kKeyF10:
|
|
case kKeyUp:
|
|
case kKeyDown:
|
|
return key;
|
|
|
|
case kKeyEscape:
|
|
// If we got an escape event, wait until the mouse buttons have been released
|
|
if (_vm->_global->_useMouse != 0)
|
|
continue;
|
|
|
|
_vm->_game->_forceHandleMouse = !_vm->_game->_forceHandleMouse;
|
|
|
|
handleMouse = false;
|
|
if ( (_vm->_game->_handleMouse != 0) &&
|
|
((_vm->_global->_useMouse != 0) || (_vm->_game->_forceHandleMouse != 0)))
|
|
handleMouse = true;
|
|
|
|
while (_vm->_global->_pressedKeys[1] != 0)
|
|
;
|
|
continue;
|
|
|
|
default:
|
|
// Got a "normal" key
|
|
|
|
uint16 savedKey = key;
|
|
|
|
key &= 0xFF;
|
|
|
|
if (((type == kTypeInputFloatNoLeave) || (type == kTypeInputFloatLeave)) &&
|
|
(key >= ' ') && (key <= 0xFF)) {
|
|
|
|
// Only allow character found in numerical floating values
|
|
|
|
const char *str1 = "0123456789-.,+ ";
|
|
const char *str2 = "0123456789-,,+ ";
|
|
|
|
if ((((savedKey >> 8) > 1) && ((savedKey >> 8) < 12)) &&
|
|
((_vm->_global->_pressedKeys[42] != 0) ||
|
|
(_vm->_global->_pressedKeys[56] != 0)))
|
|
key = ((savedKey >> 8) - 1) % 10 + '0';
|
|
|
|
int i;
|
|
for (i = 0; str1[i] != 0; i++) {
|
|
if (key == str1[i]) {
|
|
key = str2[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == (int16) strlen(str1))
|
|
key = 0;
|
|
}
|
|
|
|
if ((key >= ' ') && (key <= 0xFF)) {
|
|
if (editSize == 0) {
|
|
// Length of the string + current character + next one
|
|
int length = _vm->_draw->stringLength(str, fontIndex) +
|
|
font.getCharWidth(' ') + font.getCharWidth(key);
|
|
|
|
if (length > width)
|
|
// We're above the limit, ignore the key
|
|
continue;
|
|
|
|
if (((int32) strlen(str)) >= (_vm->_global->_inter_animDataSize * 4 - 1))
|
|
// Above the limit of character allowed in a string, ignore the key
|
|
continue;
|
|
|
|
} else {
|
|
if (strlen(str) > editSize)
|
|
// We're over the upper character limit for this field
|
|
continue;
|
|
else if (editSize == strlen(str))
|
|
// We've reached the upper limit, overwrite the last character
|
|
_vm->_util->cutFromStr(str, strlen(str) - 1, 1);
|
|
}
|
|
|
|
// Advance cursor
|
|
pos++;
|
|
tempStr[0] = key;
|
|
tempStr[1] = 0;
|
|
|
|
// Add character
|
|
_vm->_util->insertStr(tempStr, str, pos - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16 Hotspots::handleInputs(int16 time, uint16 inputCount, uint16 &curInput,
|
|
InputDesc *inputs, uint16 &id, uint16 &index) {
|
|
|
|
// Redraw all texts in all inputs we currently manage
|
|
updateAllTexts(inputs);
|
|
|
|
for (int i = 0; i < 40; i++)
|
|
WRITE_VAR(17 + i, 0);
|
|
|
|
while (1) {
|
|
// Find the hotspot index to our current input
|
|
uint16 hotspotIndex = inputToHotspot(curInput);
|
|
|
|
assert(hotspotIndex != 0xFFFF);
|
|
|
|
Hotspot inputSpot = _hotspots[hotspotIndex];
|
|
|
|
// Handle input events from that input field
|
|
uint16 key = updateInput(inputSpot.left, inputSpot.top,
|
|
inputSpot.right - inputSpot.left + 1,
|
|
inputSpot.bottom - inputSpot.top + 1,
|
|
inputs[curInput].backColor, inputs[curInput].frontColor,
|
|
GET_VARO_STR(inputSpot.key), inputs[curInput].fontIndex,
|
|
inputSpot.getType(), time, id, index);
|
|
|
|
if (_vm->_inter->_terminate)
|
|
return 0;
|
|
|
|
switch (key) {
|
|
case kKeyNone:
|
|
if (id == 0)
|
|
// No key and no hotspot => return
|
|
return 0;
|
|
|
|
if (_vm->_game->_mouseButtons != kMouseButtonsNone)
|
|
// Clicked something, get the hotspot index
|
|
index = findClickedInput(index);
|
|
|
|
if (!_hotspots[index].isInput())
|
|
// It's no input, return
|
|
return 0;
|
|
|
|
// Get the associated input index
|
|
curInput = hotspotToInput(index);
|
|
break;
|
|
|
|
case kKeyF1:
|
|
case kKeyF2:
|
|
case kKeyF3:
|
|
case kKeyF4:
|
|
case kKeyF5:
|
|
case kKeyF6:
|
|
case kKeyF7:
|
|
case kKeyF8:
|
|
case kKeyF9:
|
|
case kKeyF10:
|
|
return key;
|
|
|
|
case kKeyReturn:
|
|
// Just one input => return
|
|
if (inputCount == 1)
|
|
return kKeyReturn;
|
|
|
|
// End of input chain reached => wrap
|
|
if (curInput == (inputCount - 1)) {
|
|
curInput = 0;
|
|
break;
|
|
}
|
|
|
|
// Next input
|
|
curInput++;
|
|
break;
|
|
|
|
case kKeyDown:
|
|
// Next input
|
|
if ((inputCount - 1) > curInput)
|
|
curInput++;
|
|
break;
|
|
|
|
case kKeyUp:
|
|
// Previous input
|
|
if (curInput > 0)
|
|
curInput--;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Hotspots::evaluateNew(uint16 i, uint16 *ids, InputDesc *inputs,
|
|
uint16 &inputId, bool &hasInput, uint16 &inputCount) {
|
|
|
|
ids[i] = 0;
|
|
|
|
// Type and window
|
|
byte type = _vm->_game->_script->readByte();
|
|
byte windowNum = 0;
|
|
|
|
if ((type & 0x40) != 0) {
|
|
// Got a window ID
|
|
|
|
type -= 0x40;
|
|
windowNum = _vm->_game->_script->readByte();
|
|
}
|
|
|
|
// Coordinates
|
|
uint16 left, top, width, height, right, bottom;
|
|
uint32 funcPos = 0;
|
|
if ((type & 0x80) != 0) {
|
|
// Complex coordinate expressions
|
|
funcPos = _vm->_game->_script->pos();
|
|
left = _vm->_game->_script->readValExpr();
|
|
top = _vm->_game->_script->readValExpr();
|
|
width = _vm->_game->_script->readValExpr();
|
|
height = _vm->_game->_script->readValExpr();
|
|
} else {
|
|
// Immediate values
|
|
funcPos = 0;
|
|
left = _vm->_game->_script->readUint16();
|
|
top = _vm->_game->_script->readUint16();
|
|
width = _vm->_game->_script->readUint16();
|
|
height = _vm->_game->_script->readUint16();
|
|
}
|
|
type &= 0x7F;
|
|
|
|
if (_vm->getGameType() == kGameTypeAdibou1 &&
|
|
left == 0xFFFF - 5 &&
|
|
(_vm->isCurrentTot("L61EXO-7.tot") || _vm->isCurrentTot("L61EXO-9.tot"))) {
|
|
// WORKAROUND: In those "find matching cards" games, hotspots of cards already found
|
|
// are normally removed by setting their x coordinate to 0xFFFF. However, sometimes
|
|
// the hotspot pos() function subtracts an additional 5 from x, which results in the
|
|
// hotspot not being properly removed (and clicking on it leads to a crash).
|
|
// TODO: the original executable somehow avoids this problem.
|
|
left = 0xFFFF;
|
|
}
|
|
|
|
// Draw a border around the hotspot
|
|
if (_vm->_draw->_renderFlags & RENDERFLAG_BORDERHOTSPOTS) {
|
|
Surface &surface = *_vm->_draw->_spritesArray[_vm->_draw->_destSurface];
|
|
|
|
_vm->_video->dirtyRectsAll();
|
|
|
|
if (windowNum == 0) {
|
|
// The hotspot is not inside a window, just draw border it
|
|
surface.drawRect(left, top, left + width - 1, top + height - 1, 0);
|
|
|
|
} else {
|
|
// The hotspot is inside a window, only draw it if it's the topmost window
|
|
|
|
if ((_vm->_draw->_fascinWin[windowNum].id != -1) &&
|
|
(_vm->_draw->_fascinWin[windowNum].id == (_vm->_draw->_winCount - 1))) {
|
|
|
|
const uint16 wLeft = left + _vm->_draw->_fascinWin[windowNum].left;
|
|
const uint16 wTop = top + _vm->_draw->_fascinWin[windowNum].top;
|
|
|
|
surface.drawRect(wLeft, wTop, wLeft + width - 1, wTop + height - 1, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply global drawing offset
|
|
if ((_vm->_draw->_renderFlags & RENDERFLAG_CAPTUREPOP) && (left != 0xFFFF)) {
|
|
left += _vm->_draw->_backDeltaX;
|
|
top += _vm->_draw->_backDeltaY;
|
|
}
|
|
|
|
if (left != 0xFFFF) {
|
|
_vm->_draw->adjustCoords(0, &left, &top);
|
|
if ((type & 0x3F) >= 20 || (type & 0x3F) < 3)
|
|
_vm->_draw->adjustCoords(0, &width, &height);
|
|
else {
|
|
if (_vm->_draw->_needAdjust != 2 && _vm->_draw->_needAdjust != 10)
|
|
height &= 0xFFFE;
|
|
|
|
_vm->_draw->adjustCoords(0, &height, 0);
|
|
}
|
|
}
|
|
|
|
right = left + width - 1;
|
|
bottom = top + height - 1;
|
|
|
|
// Enabling the hotspots again
|
|
if ((type == kTypeEnable2) || (type == kTypeEnable1)) {
|
|
uint8 wantedState = 0;
|
|
if (type == kTypeEnable2)
|
|
wantedState = kStateFilledDisabled | kStateType2;
|
|
else
|
|
wantedState = kStateFilledDisabled | kStateType1;
|
|
|
|
_vm->_game->_script->skip(6);
|
|
|
|
for (int j = 0; j < kHotspotCount; j++) {
|
|
Hotspot &spot = _hotspots[j];
|
|
|
|
if (spot.getState() == wantedState) {
|
|
spot.enable();
|
|
spot.funcEnter = _vm->_game->_script->pos();
|
|
spot.funcLeave = _vm->_game->_script->pos();
|
|
}
|
|
}
|
|
|
|
_vm->_game->_script->skipBlock();
|
|
|
|
return;
|
|
}
|
|
|
|
int16 key = 0;
|
|
int16 flags = 0;
|
|
Font *font = nullptr;
|
|
uint32 funcEnter = 0, funcLeave = 0;
|
|
|
|
if ((windowNum != 0) && (type != 0) && (type != 2))
|
|
debugC(0, kDebugHotspots, "evaluateNew - type %d, win %d",type, windowNum);
|
|
|
|
// Evaluate parameters for the new hotspot
|
|
switch (type) {
|
|
case kTypeNone:
|
|
_vm->_game->_script->skip(6);
|
|
|
|
funcEnter = _vm->_game->_script->pos();
|
|
_vm->_game->_script->skipBlock();
|
|
|
|
funcLeave = _vm->_game->_script->pos();
|
|
_vm->_game->_script->skipBlock();
|
|
|
|
key = i + ((kStateFilled | kStateType2) << 12);
|
|
flags = type + (windowNum << 8);
|
|
break;
|
|
|
|
case kTypeMove:
|
|
key = _vm->_game->_script->readInt16();
|
|
ids[i] = _vm->_game->_script->readInt16();
|
|
flags = _vm->_game->_script->readInt16();
|
|
|
|
funcEnter = _vm->_game->_script->pos();
|
|
_vm->_game->_script->skipBlock();
|
|
|
|
funcLeave = _vm->_game->_script->pos();
|
|
_vm->_game->_script->skipBlock();
|
|
|
|
if (key == 0)
|
|
key = i + ((kStateFilled | kStateType2) << 12);
|
|
|
|
flags = type + (windowNum << 8) + (flags << 4);
|
|
break;
|
|
|
|
case kTypeInput1NoLeave:
|
|
case kTypeInput1Leave:
|
|
case kTypeInput2NoLeave:
|
|
case kTypeInput2Leave:
|
|
case kTypeInput3NoLeave:
|
|
case kTypeInput3Leave:
|
|
case kTypeInputFloatNoLeave:
|
|
case kTypeInputFloatLeave:
|
|
hasInput = true;
|
|
|
|
_vm->_util->clearKeyBuf();
|
|
|
|
// Input text parameters
|
|
key = _vm->_game->_script->readVarIndex();
|
|
inputs[inputCount].fontIndex = _vm->_game->_script->readInt16();
|
|
inputs[inputCount].backColor = _vm->_game->_script->readByte();
|
|
inputs[inputCount].frontColor = _vm->_game->_script->readByte();
|
|
inputs[inputCount].length = 0;
|
|
inputs[inputCount].str = nullptr;
|
|
|
|
if ((type >= kTypeInput2NoLeave) && (type <= kTypeInput3Leave)) {
|
|
inputs[inputCount].length = _vm->_game->_script->readUint16();
|
|
|
|
inputs[inputCount].str =
|
|
(const char *)(_vm->_game->_script->getData() + _vm->_game->_script->pos());
|
|
|
|
_vm->_game->_script->skip(inputs[inputCount].length);
|
|
}
|
|
|
|
if (left == 0xFFFF) {
|
|
if (!(type & 1))
|
|
// No coordinates but a leave block => skip it
|
|
_vm->_game->_script->skipBlock();
|
|
break;
|
|
}
|
|
|
|
font = _vm->_draw->_fonts[inputs[inputCount].fontIndex];
|
|
if (font->isMonospaced())
|
|
right = left + width * font->getCharWidth() - 1;
|
|
|
|
funcEnter = 0;
|
|
funcPos = 0;
|
|
funcLeave = 0;
|
|
if (!(type & 1)) {
|
|
// Got a leave
|
|
funcLeave = _vm->_game->_script->pos();
|
|
_vm->_game->_script->skipBlock();
|
|
}
|
|
|
|
flags = type;
|
|
|
|
inputCount++;
|
|
break;
|
|
|
|
case 20:
|
|
inputId = i;
|
|
// fall through
|
|
case kTypeClick:
|
|
key = _vm->_game->_script->readInt16();
|
|
ids[i] = _vm->_game->_script->readInt16();
|
|
flags = _vm->_game->_script->readInt16();
|
|
|
|
if (flags > 3)
|
|
warning("evaluateNew: Warning, use of type 2 or 20. flags = %d, should be %d", flags, flags&3);
|
|
|
|
funcEnter = 0;
|
|
|
|
funcLeave = _vm->_game->_script->pos();
|
|
_vm->_game->_script->skipBlock();
|
|
|
|
flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
|
|
break;
|
|
|
|
case kTypeClickEnter:
|
|
key = _vm->_game->_script->readInt16();
|
|
ids[i] = _vm->_game->_script->readInt16();
|
|
flags = _vm->_game->_script->readInt16() & 3;
|
|
|
|
funcEnter = _vm->_game->_script->pos();
|
|
_vm->_game->_script->skipBlock();
|
|
|
|
funcLeave = 0;
|
|
|
|
flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Add the new hotspot
|
|
add(i | (kStateFilled << 12), left, top, right, bottom,
|
|
flags, key, funcEnter, funcLeave, funcPos);
|
|
}
|
|
|
|
bool Hotspots::evaluateFind(uint16 key, int16 timeVal, const uint16 *ids,
|
|
uint16 leaveWindowIndex, uint16 hotspotIndex1, uint16 hotspotIndex2,
|
|
uint16 endIndex, int16 &duration, uint16 &id, uint16 &index, bool &finished) {
|
|
|
|
bool fascinCheck = false;
|
|
|
|
if (id != 0)
|
|
// We already found a hotspot, nothing to do
|
|
return true;
|
|
|
|
if (key != 0) {
|
|
// We've got a key
|
|
|
|
// Find the hotspot with that key associated
|
|
findKey(key, id, index);
|
|
if (id != 0)
|
|
// Found it
|
|
return true;
|
|
|
|
// Try it case insensitively
|
|
findKeyCaseInsensitive(key, id, index);
|
|
if (id != 0)
|
|
// Found it
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
if ((_vm->getGameType() == kGameTypeFascination) && (getCurrentHotspot()))
|
|
fascinCheck = true;
|
|
|
|
if ((duration != 0) && (!fascinCheck)) {
|
|
// We've got a time duration
|
|
|
|
if (hotspotIndex1 != 0) {
|
|
finished =
|
|
leaveNthPlain(hotspotIndex1, endIndex, timeVal, ids, id, index, duration);
|
|
} else if (hotspotIndex2 != 0) {
|
|
findNthPlain(hotspotIndex2, endIndex, id, index);
|
|
} else {
|
|
// Enter the first hotspot
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
Hotspot &spot = _hotspots[i];
|
|
if (spot.isFilledNew()) {
|
|
id = spot.id;
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Leave the current hotspot
|
|
if ((_currentKey != 0) && (_hotspots[_currentIndex].funcLeave != 0))
|
|
call(_hotspots[_currentIndex].funcLeave);
|
|
|
|
_currentKey = 0;
|
|
}
|
|
|
|
if (id != 0)
|
|
return true;
|
|
|
|
return false;
|
|
} else {
|
|
if (leaveWindowIndex != 0)
|
|
findNthPlain(leaveWindowIndex, endIndex, id, index);
|
|
|
|
if (id != 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Hotspots::evaluate() {
|
|
InputDesc inputs[20];
|
|
uint16 ids[kHotspotCount];
|
|
|
|
// Push all local hotspots
|
|
push(0);
|
|
|
|
// Find the current end of the hotspot block
|
|
uint16 endIndex = 0;
|
|
while (!_hotspots[endIndex].isEnd())
|
|
endIndex++;
|
|
|
|
_shouldPush = false;
|
|
|
|
_vm->_game->_script->skip(1);
|
|
|
|
// Number of new hotspots
|
|
byte count = _vm->_game->_script->readByte();
|
|
|
|
// Parameters of this block
|
|
_vm->_game->_handleMouse = _vm->_game->_script->peekByte(0);
|
|
int16 duration = _vm->_game->_script->peekByte(1);
|
|
|
|
byte leaveWindowIndex = 0;
|
|
if (_vm->getGameType() == kGameTypeFascination)
|
|
leaveWindowIndex = _vm->_game->_script->peekByte(2);
|
|
|
|
byte hotspotIndex1 = _vm->_game->_script->peekByte(3);
|
|
byte hotspotIndex2 = _vm->_game->_script->peekByte(4);
|
|
bool needRecalculation = _vm->_game->_script->peekByte(5) != 0;
|
|
|
|
// Seconds -> Milliseconds
|
|
duration *= 1000;
|
|
|
|
if ((hotspotIndex1 != 0) || (hotspotIndex2 != 0)) {
|
|
duration /= 100;
|
|
if (_vm->_game->_script->peekByte(1) == 100)
|
|
duration = 2;
|
|
}
|
|
|
|
int16 timeVal = duration;
|
|
|
|
_vm->_game->_script->skip(6);
|
|
|
|
setCurrentHotspot(nullptr, 0);
|
|
|
|
bool finishedDuration = false;
|
|
|
|
uint16 id = 0;
|
|
uint16 inputId = 0xFFFF;
|
|
uint16 index = 0;
|
|
|
|
bool hasInput = false;
|
|
uint16 inputCount = 0;
|
|
|
|
// Adding new hotspots
|
|
for (uint16 i = 0; i < count; i++)
|
|
evaluateNew(i, ids, inputs, inputId, hasInput, inputCount);
|
|
|
|
// Recalculate all hotspots if requested
|
|
if (needRecalculation)
|
|
recalculate(true);
|
|
|
|
_vm->_game->_forceHandleMouse = 0;
|
|
_vm->_util->clearKeyBuf();
|
|
|
|
while ((id == 0) && !_vm->_inter->_terminate && !_vm->shouldQuit()) {
|
|
uint16 key = 0;
|
|
if (hasInput) {
|
|
// Input
|
|
|
|
uint16 curInput = 0;
|
|
|
|
key = handleInputs(duration, inputCount, curInput, inputs, id, index);
|
|
|
|
// Notify the script of the current input index
|
|
WRITE_VAR(17 + 38, curInput);
|
|
if (key == kKeyReturn) {
|
|
// Return pressed, invoke input leave
|
|
findFirstInputLeave(id, inputId, index);
|
|
break;
|
|
}
|
|
} else
|
|
// Normal move or click check
|
|
key = check(_vm->_game->_handleMouse, -duration, id, index);
|
|
|
|
key = convertSpecialKey(key);
|
|
|
|
// Try to find a fitting hotspot
|
|
evaluateFind(key, timeVal, ids, leaveWindowIndex, hotspotIndex1, hotspotIndex2, endIndex,
|
|
duration, id, index, finishedDuration);
|
|
|
|
if (finishedDuration)
|
|
break;
|
|
|
|
if ((id == 0) || (_hotspots[index].funcLeave != 0))
|
|
// We don't have a new ID, but haven't yet handled the leave function
|
|
continue;
|
|
|
|
_vm->_inter->storeMouse();
|
|
|
|
setCurrentHotspot(ids, id);
|
|
|
|
// Enter it
|
|
if (_hotspots[index].funcEnter != 0)
|
|
call(_hotspots[index].funcEnter);
|
|
|
|
setCurrentHotspot(nullptr, 0);
|
|
id = 0;
|
|
}
|
|
|
|
if ((id & 0xFFF) == inputId)
|
|
matchInputStrings(inputs);
|
|
|
|
if (_vm->_game->_handleMouse == 1)
|
|
_vm->_draw->blitCursor();
|
|
|
|
if (!_vm->_inter->_terminate && (!finishedDuration)) {
|
|
_vm->_game->_script->seek(_hotspots[index].funcLeave);
|
|
|
|
_vm->_inter->storeMouse();
|
|
if (getCurrentHotspot() == 0) {
|
|
// No hotspot currently handled, now we'll handle the newly found one
|
|
|
|
setCurrentHotspot(ids, id);
|
|
}
|
|
} else
|
|
_vm->_game->_script->setFinished(true);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
// Remove all local hotspots
|
|
remove(i + (kStateFilled << 12));
|
|
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
Hotspot &spot = _hotspots[i];
|
|
|
|
// Disable the ones still there
|
|
if ((spot.getState() == (kStateFilled | kStateType1)) ||
|
|
(spot.getState() == (kStateFilled | kStateType2)))
|
|
spot.disable();
|
|
}
|
|
|
|
}
|
|
|
|
int16 Hotspots::findCursor(uint16 x, uint16 y) const {
|
|
int16 cursor = 0;
|
|
|
|
int16 deltax = 0;
|
|
int16 deltay = 0;
|
|
|
|
// Fascination uses hard-coded windows
|
|
if (_vm->getGameType() == kGameTypeFascination) {
|
|
cursor = windowCursor(deltax, deltay);
|
|
|
|
// We're in a window and in an area that forces a specific cursor
|
|
if (cursor > 0)
|
|
return cursor;
|
|
|
|
// We're somewhere else inside a window
|
|
if (cursor < 0) {
|
|
int16 curType = -cursor * 256;
|
|
cursor = 0;
|
|
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
// this check is /really/ Fascination specific.
|
|
// It's illogical, so if it's to be reused in Adi games... Be careful!
|
|
if ((spot.flags & 0xFF00) == curType)
|
|
if (spot.isIn(x - deltax, y - deltay)) {
|
|
if (spot.getType() < kTypeInput1NoLeave)
|
|
cursor = 1;
|
|
else
|
|
cursor = 3;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_vm->_draw->_cursorAnimLow[cursor] == -1)
|
|
// If the cursor is invalid... there's a generic "click" cursor
|
|
cursor = 1;
|
|
|
|
return cursor;
|
|
}
|
|
|
|
}
|
|
|
|
// Normal, non-window cursor handling
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if ((spot.getWindow() != 0) || spot.isDisabled())
|
|
// Ignore disabled and non-main-windowed hotspots
|
|
continue;
|
|
|
|
if (!spot.isIn(x, y))
|
|
// We're not in that hotspot, ignore it
|
|
continue;
|
|
|
|
if (spot.getCursor() == 0) {
|
|
// Hotspot doesn't itself specify a cursor...
|
|
if (spot.getType() >= kTypeInput1NoLeave) {
|
|
// ...but the type has a generic one
|
|
cursor = 3;
|
|
break;
|
|
} else if ((spot.getButton() != kMouseButtonsRight) && (cursor == 0))
|
|
// ...but there's a generic "click" cursor
|
|
cursor = 1;
|
|
} else if (cursor == 0)
|
|
// Hotspot had an attached cursor index
|
|
cursor = spot.getCursor();
|
|
}
|
|
|
|
return cursor;
|
|
}
|
|
|
|
void Hotspots::oPlaytoons_F_1B() {
|
|
int16 shortId;
|
|
int16 longId;
|
|
int16 var2;
|
|
int16 fontIndex;
|
|
int16 var4;
|
|
|
|
uint16 left;
|
|
uint16 top;
|
|
uint16 right;
|
|
uint16 bottom;
|
|
|
|
shortId = _vm->_game->_script->readValExpr();
|
|
var2 = _vm->_game->_script->readValExpr();
|
|
|
|
_vm->_game->_script->evalExpr(nullptr);
|
|
|
|
fontIndex = _vm->_game->_script->readValExpr();
|
|
var4 = _vm->_game->_script->readValExpr();
|
|
|
|
// this variable is always set to 0 in Playtoons
|
|
// var_4 += unk_var;
|
|
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
if (_hotspots[i].isEnd())
|
|
return;
|
|
|
|
if ((_hotspots[i].id == 0xD000 + shortId) || (_hotspots[i].id == 0xB000 + shortId) ||
|
|
(_hotspots[i].id == 0x4000 + shortId)) {
|
|
longId = _hotspots[i].id;
|
|
warning("oPlaytoons_F_1B: shortId %d, var2 %d fontIndex %d var4 %d - longId %d", shortId, var2, fontIndex, var4, longId);
|
|
|
|
left = _hotspots[i].left;
|
|
top = _hotspots[i].top;
|
|
right = _hotspots[i].right;
|
|
bottom = _hotspots[i].bottom;
|
|
|
|
left += 2;
|
|
top += 2;
|
|
right -= 2;
|
|
bottom -= 2;
|
|
if ((_vm->_draw->_needAdjust != 2) && (_vm->_draw->_needAdjust != 10)) {
|
|
left += 2;
|
|
top += 2;
|
|
right -= 2;
|
|
bottom -= 2;
|
|
}
|
|
_vm->_draw->oPlaytoons_sub_F_1B(0x8000+ var2, left, top, right, bottom, _vm->_game->_script->getResultStr(), fontIndex, var4, shortId);
|
|
return;
|
|
}
|
|
}
|
|
warning("shortId not found %d", shortId);
|
|
return;
|
|
}
|
|
|
|
uint16 Hotspots::inputToHotspot(uint16 input) const {
|
|
uint16 inputIndex = 0;
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (!spot.isActiveInput())
|
|
// Not an active input
|
|
continue;
|
|
|
|
if (inputIndex == input)
|
|
// We've found our input
|
|
return i;
|
|
|
|
// Next one
|
|
inputIndex++;
|
|
}
|
|
|
|
// None found
|
|
return 0xFFFF;
|
|
}
|
|
|
|
uint16 Hotspots::hotspotToInput(uint16 hotspot) const {
|
|
uint16 input = 0;
|
|
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (!spot.isActiveInput())
|
|
// Not an active input
|
|
continue;
|
|
|
|
if (i == hotspot)
|
|
// We've found our hotspot
|
|
break;
|
|
|
|
// Next one
|
|
input++;
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
uint16 Hotspots::findClickedInput(uint16 index) const {
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (spot.getWindow() != 0)
|
|
// Ignore other windows
|
|
continue;
|
|
|
|
if (spot.isDisabled())
|
|
// Ignore disabled hotspots
|
|
continue;
|
|
|
|
if (!spot.isIn(_vm->_global->_inter_mouseX, _vm->_global->_inter_mouseY))
|
|
// This one wasn't it
|
|
continue;
|
|
|
|
if (spot.getCursor() != 0)
|
|
// This one specifies a cursor, so we don't want it
|
|
continue;
|
|
|
|
if (!spot.isInput())
|
|
// It's no input
|
|
continue;
|
|
|
|
index = i;
|
|
break;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
bool Hotspots::findFirstInputLeave(uint16 &id, uint16 &inputId, uint16 &index) const {
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (!spot.isFilledEnabled())
|
|
// Not filled or disabled
|
|
continue;
|
|
|
|
if (!spot.isInputLeave())
|
|
// Not an input with a leave function
|
|
continue;
|
|
|
|
id = spot.id;
|
|
inputId = spot.id & 0x7FFF;
|
|
index = i;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Hotspots::findKey(uint16 key, uint16 &id, uint16 &index) const {
|
|
id = 0;
|
|
index = 0;
|
|
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (!spot.isFilledEnabled())
|
|
// Not filled or disabled
|
|
continue;
|
|
|
|
// Key match Catch all
|
|
if ((spot.key == key) || (spot.key == 0x7FFF)) {
|
|
id = spot.id;
|
|
index = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Hotspots::findKeyCaseInsensitive(uint16 key, uint16 &id, uint16 &index) const {
|
|
id = 0;
|
|
index = 0;
|
|
|
|
for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (!spot.isFilledEnabled())
|
|
// Not filled or disabled, ignore
|
|
continue;
|
|
|
|
if ((spot.key & 0xFF00) != 0)
|
|
continue;
|
|
|
|
if (spot.key == 0)
|
|
// No associated key, ignore
|
|
continue;
|
|
|
|
// Compare
|
|
if (toupper(key & 0xFF) == toupper(spot.key)) {
|
|
id = spot.id;
|
|
index = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Hotspots::findNthPlain(uint16 n, uint16 startIndex, uint16 &id, uint16 &index) const {
|
|
id = 0;
|
|
index = 0;
|
|
|
|
uint16 counter = 0;
|
|
for (int i = startIndex; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (!spot.isFilledNew())
|
|
// Not filled, ignore
|
|
continue;
|
|
|
|
if (++counter != n)
|
|
// Not yet the one we want
|
|
continue;
|
|
|
|
id = spot.id;
|
|
index = i;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Hotspots::leaveNthPlain(uint16 n, uint16 startIndex, int16 timeVal, const uint16 *ids,
|
|
uint16 &id, uint16 &index, int16 &duration) {
|
|
|
|
id = 0;
|
|
index = 0;
|
|
|
|
if (!findNthPlain(n, startIndex, id, index))
|
|
// Doesn't exist
|
|
return false;
|
|
|
|
_vm->_inter->storeMouse();
|
|
|
|
if (getCurrentHotspot() != 0)
|
|
// We already handle a hotspot
|
|
return false;
|
|
|
|
setCurrentHotspot(ids, id);
|
|
|
|
const Hotspot &spot = _hotspots[index];
|
|
if (spot.funcLeave != 0) {
|
|
// It has a leave function
|
|
|
|
uint32 startTime, callTime;
|
|
|
|
// Call the leave and time it
|
|
startTime = _vm->_util->getTimeKey();
|
|
call(spot.funcLeave);
|
|
_vm->_inter->animPalette();
|
|
callTime = _vm->_util->getTimeKey() - startTime;
|
|
|
|
// Remove the time it took from the time we have available
|
|
duration = CLIP<int>(timeVal - callTime, 2, timeVal);
|
|
}
|
|
|
|
if (getCurrentHotspot() == 0) {
|
|
id = 0;
|
|
index = 0;
|
|
}
|
|
|
|
return getCurrentHotspot() != 0;
|
|
}
|
|
|
|
void Hotspots::setCurrentHotspot(const uint16 *ids, uint16 id) const {
|
|
if (!ids) {
|
|
WRITE_VAR(16, 0);
|
|
return;
|
|
}
|
|
|
|
if (Hotspot::getState(id) == kStateFilled)
|
|
WRITE_VAR(16, (int16)ids[id & 0xFFF]);
|
|
else
|
|
WRITE_VAR(16, id & 0xFFF);
|
|
}
|
|
|
|
uint32 Hotspots::getCurrentHotspot() const {
|
|
return VAR(16);
|
|
}
|
|
|
|
void Hotspots::cleanFloatString(const Hotspot &spot) const {
|
|
char *to, *from;
|
|
|
|
to = from = GET_VARO_STR(spot.key);
|
|
for (int i = 0; (i < 257) && (*from != '\0'); i++, from++) {
|
|
char c = *from;
|
|
|
|
// Skip spaces
|
|
if (c == ' ')
|
|
continue;
|
|
|
|
// Convert decimal separator if necessary
|
|
if ((_vm->_global->_language == kLanguageBritish) && (c == '.'))
|
|
c = ',';
|
|
|
|
*to++ = c;
|
|
}
|
|
|
|
*to = '\0';
|
|
}
|
|
|
|
void Hotspots::checkStringMatch(const Hotspot &spot, const InputDesc &input,
|
|
uint16 inputPos) const {
|
|
|
|
const char *str = input.str;
|
|
|
|
char tempStr[256];
|
|
char spotStr[256];
|
|
|
|
Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);
|
|
|
|
if (spot.getType() < kTypeInput3NoLeave)
|
|
_vm->_util->cleanupStr(tempStr);
|
|
|
|
uint16 pos = 0;
|
|
do {
|
|
Common::strlcpy(spotStr, str, 256);
|
|
|
|
pos += strlen(str) + 1;
|
|
str += strlen(str) + 1;
|
|
|
|
if (spot.getType() < kTypeInput3NoLeave)
|
|
_vm->_util->cleanupStr(spotStr);
|
|
|
|
// Compare the entered string with the string we wanted
|
|
if (strcmp(tempStr, spotStr) == 0) {
|
|
WRITE_VAR(17, VAR(17) + 1);
|
|
WRITE_VAR(17 + inputPos, 1);
|
|
break;
|
|
}
|
|
} while (input.length > pos);
|
|
}
|
|
|
|
void Hotspots::matchInputStrings(const InputDesc *inputs) const {
|
|
uint16 strInputCount = 0;
|
|
uint16 inputIndex = 0;
|
|
uint16 inputPos = 1;
|
|
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
// Looking for all enabled inputs
|
|
if (spot.isEnd())
|
|
continue;
|
|
if (!spot.isFilledEnabled())
|
|
continue;
|
|
if (!spot.isInput())
|
|
continue;
|
|
|
|
if (spot.getType() >= kTypeInputFloatNoLeave)
|
|
cleanFloatString(spot);
|
|
|
|
if ((spot.getType() >= kTypeInput2NoLeave) && (spot.getType() <= kTypeInput3Leave)) {
|
|
// Look if we find a match between the wanted and the typed string
|
|
checkStringMatch(spot, inputs[inputIndex], inputPos);
|
|
strInputCount++;
|
|
} else
|
|
WRITE_VAR(17 + inputPos, 2);
|
|
|
|
inputIndex++;
|
|
inputPos++;
|
|
}
|
|
|
|
// Notify the scripts if we reached the requested hotspot
|
|
WRITE_VAR(17, (uint32) (strInputCount == ((uint16) VAR(17))));
|
|
}
|
|
|
|
uint16 Hotspots::convertSpecialKey(uint16 key) const {
|
|
if (((key & 0xFF) >= ' ') && ((key & 0xFF) <= 0xFF) &&
|
|
((key >> 8) > 1) && ((key >> 8) < 12))
|
|
key = '0' + (((key >> 8) - 1) % 10) + (key & 0xFF00);
|
|
|
|
return key;
|
|
}
|
|
|
|
void Hotspots::getTextCursorPos(const Font &font, const char *str,
|
|
uint32 pos, uint16 x, uint16 y, uint16 width, uint16 height,
|
|
uint16 &cursorX, uint16 &cursorY, uint16 &cursorWidth, uint16 &cursorHeight) const {
|
|
|
|
if (!font.isMonospaced()) {
|
|
// Cursor to the right of the current character
|
|
|
|
cursorX = x;
|
|
cursorY = y;
|
|
cursorWidth = 1;
|
|
cursorHeight = height;
|
|
|
|
// Iterate through the string and add each character's width
|
|
for (uint32 i = 0; i < pos; i++)
|
|
cursorX += font.getCharWidth(str[i]);
|
|
|
|
} else {
|
|
// Cursor underlining the current character
|
|
|
|
cursorX = x + font.getCharWidth() * pos;
|
|
cursorY = y + height - 1;
|
|
cursorWidth = font.getCharWidth();
|
|
cursorHeight = 1;
|
|
}
|
|
}
|
|
|
|
void Hotspots::fillRect(uint16 x, uint16 y, uint16 width, uint16 height, uint16 color) const {
|
|
_vm->_draw->_destSurface = Draw::kBackSurface;
|
|
_vm->_draw->_destSpriteX = x;
|
|
_vm->_draw->_destSpriteY = y;
|
|
_vm->_draw->_spriteRight = width;
|
|
_vm->_draw->_spriteBottom = height;
|
|
_vm->_draw->_backColor = color;
|
|
|
|
_vm->_draw->spriteOperation(DRAW_FILLRECT | 0x10);
|
|
}
|
|
|
|
void Hotspots::printText(uint16 x, uint16 y, const char *str, uint16 fontIndex, uint16 color) const {
|
|
_vm->_draw->_destSpriteX = x;
|
|
_vm->_draw->_destSpriteY = y;
|
|
_vm->_draw->_frontColor = color;
|
|
_vm->_draw->_fontIndex = fontIndex;
|
|
_vm->_draw->_textToPrint = str;
|
|
_vm->_draw->_transparency = 1;
|
|
|
|
_vm->_draw->spriteOperation(DRAW_PRINTTEXT | 0x10);
|
|
}
|
|
|
|
void Hotspots::updateAllTexts(const InputDesc *inputs) const {
|
|
uint16 input = 0;
|
|
|
|
for (int i = 0; i < kHotspotCount; i++) {
|
|
const Hotspot &spot = _hotspots[i];
|
|
|
|
if (spot.isEnd())
|
|
// It's an end, we don't want it
|
|
continue;
|
|
|
|
if (!spot.isFilledEnabled())
|
|
// This one's either not used or disabled
|
|
continue;
|
|
|
|
if (!spot.isInput())
|
|
// Not an input
|
|
continue;
|
|
|
|
// Get its text
|
|
char tempStr[256];
|
|
Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);
|
|
|
|
// Coordinates
|
|
uint16 x = spot.left;
|
|
uint16 y = spot.top;
|
|
uint16 width = spot.right - spot.left + 1;
|
|
uint16 height = spot.bottom - spot.top + 1;
|
|
// Clear the background
|
|
fillRect(x, y, width, height, inputs[input].backColor);
|
|
|
|
// Center the text vertically
|
|
y += (height - _vm->_draw->_fonts[_vm->_draw->_fontIndex]->getCharHeight()) / 2;
|
|
|
|
// Draw it
|
|
printText(x, y, tempStr, inputs[input].fontIndex, inputs[input].frontColor);
|
|
|
|
input++;
|
|
}
|
|
}
|
|
} // End of namespace Gob
|