scummvm/gui/about.cpp
Kaloyan Chehlarski db6b2a11cc GUI: Make sure About dialog can scroll to top
Fixed an issue where scrolling up in the About dialog would
stop before the top of the text, sometimes cropping
parts of it.
2023-11-14 12:05:27 +02:00

1490 lines
34 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/>.
*
*/
#include "engines/metaengine.h"
#include "base/plugins.h"
#include "base/version.h"
#include "common/events.h"
#include "common/system.h"
#include "common/translation.h"
#include "common/util.h"
#include "graphics/surface.h"
#include "graphics/fonts/amigafont.h"
#include "gui/about.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
namespace GUI {
class EE;
class EEHandler {
public:
bool handleKeyDown(Common::KeyState &state);
};
enum {
kScrollStartDelay = 1500,
kScrollMillisPerPixel = 60
};
// Every Line should start with a letter followed by a digit. Currently those can be
// (all subject to change)
// Letter:
// C, L, R -- set center/left/right alignment
// A -- ASCII text to replace the next (latin1) line
// Digit:
// 0 - 2 -- set a custom color:
// 0 normal text
// 1 highlighted text
// 2 disabled text
// TODO: Maybe add a tab/indent feature; that is, make it possible to specify
// an amount by which that line shall be indented (the indent of course would have
// to be considered while performing any word wrapping, too).
//
// TODO: Add different font sizes (for bigger headlines)
// TODO: Allow color change in the middle of a line...
static const char *copyright_text[] = {
"",
"C0""Copyright (C) 2001-2023 The ScummVM Team",
"C0""https://www.scummvm.org",
"",
"C0""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 binary.",
"",
};
static const char *gpl_text[] = {
"",
"C0""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.",
"C0""",
"C0""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.",
"",
"C0""You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.",
"",
};
#include "gui/credits.h"
AboutDialog::AboutDialog()
: Dialog(10, 20, 300, 174),
_scrollPos(0), _scrollTime(0), _willClose(false), _autoScroll(true) {
reflowLayout();
int i;
for (i = 0; i < 1; i++)
_lines.push_back(Common::U32String());
Common::String version("C0""ScummVM ");
version += gScummVMVersion;
addLine(version);
// I18N: built on <build date> with <compiler>
Common::U32String date = Common::U32String::format(_("(built on %s with %s)"), gScummVMBuildDate, gScummVMCompiler);
addLine(Common::U32String("C2") + date);
for (i = 0; i < ARRAYSIZE(copyright_text); i++)
addLine(Common::U32String(copyright_text[i]));
Common::U32String features("C1");
features += _("Features compiled in:");
addLine(features);
Common::String featureList("C0");
featureList += gScummVMFeatures;
addLine(featureList);
_lines.push_back(Common::U32String());
Common::U32String extensionSupportString[3] = { _("not supported"), _("disabled"), _("enabled") };
byte sse2Support = 0;
byte avx2Support = 0;
byte neonSupport = 0;
#ifdef SCUMMVM_SSE2
++sse2Support;
if (g_system->hasFeature(OSystem::kFeatureCpuSSE2))
++sse2Support;
#endif
#ifdef SCUMMVM_AVX2
++avx2Support;
if (g_system->hasFeature(OSystem::kFeatureCpuAVX2))
++avx2Support;
#endif
#ifdef SCUMMVM_NEON
++neonSupport;
if (g_system->hasFeature(OSystem::kFeatureCpuNEON))
++neonSupport;
#endif
Common::U32String extensionsInfo("C1");
// I18N: CPU extensions are sets of extra processor instructions used to speed up operations. See Intel AVX2, ARM NEON, etc.
extensionsInfo += _("CPU extensions support:");
addLine(extensionsInfo);
Common::U32String compiledExtensionsList("C0");
compiledExtensionsList += Common::U32String::format("SSE2(%S) AVX2(%S) NEON(%S)",
extensionSupportString[sse2Support].c_str(),
extensionSupportString[avx2Support].c_str(),
extensionSupportString[neonSupport].c_str());
addLine(compiledExtensionsList);
_lines.push_back(Common::U32String());
Common::U32String engines("C1");
engines += _("Available engines:");
addLine(engines);
const PluginList &plugins = EngineMan.getPlugins(PLUGIN_TYPE_ENGINE);
PluginList::const_iterator iter = plugins.begin();
for (; iter != plugins.end(); ++iter) {
Common::String str;
const Plugin *p = EngineMan.findPlugin((*iter)->getName());
if (!p) {
warning("Cannot find plugin for %s", (*iter)->getName());
continue;
}
str = "C0";
str += p->get<MetaEngineDetection>().getEngineName();
addLine(str);
str = "C2";
str += p->get<MetaEngineDetection>().getOriginalCopyright();
addLine(str);
//addLine("");
}
for (i = 0; i < ARRAYSIZE(gpl_text); i++)
addLine(Common::U32String(gpl_text[i]));
_lines.push_back(Common::U32String());
for (i = 0; i < ARRAYSIZE(credits); i++)
addLine(Common::U32String(credits[i], Common::kUtf8));
}
void AboutDialog::addLine(const Common::U32String &str) {
Common::U32String::const_iterator strBeginItr = str.begin();
if (*strBeginItr == 0) {
_lines.push_back(Common::U32String());
} else {
Common::U32String format(str.begin(), str.begin() + 2);
strBeginItr += 2;
Common::U32String renderStr(strBeginItr, str.end());
Common::U32StringArray wrappedLines;
g_gui.getFont().wordWrapText(renderStr, _w - 2 * _xOff, wrappedLines);
for (Common::U32StringArray::const_iterator i = wrappedLines.begin(); i != wrappedLines.end(); ++i) {
_lines.push_back(format + *i);
}
}
}
void AboutDialog::open() {
_scrollTime = g_system->getMillis() + kScrollStartDelay;
_scrollPos = 0;
_willClose = false;
Dialog::open();
}
void AboutDialog::close() {
Dialog::close();
}
void AboutDialog::drawDialog(DrawLayer layerToDraw) {
Dialog::drawDialog(layerToDraw);
setTextDrawableArea(Common::Rect(_x, _y, _x + _w, _y + _h));
// Draw text
// TODO: Add a "fade" effect for the top/bottom text lines
// TODO: Maybe prerender all of the text into another surface,
// and then simply compose that over the screen surface
// in the right way. Should be even faster...
const int firstLine = _scrollPos / _lineHeight;
const int lastLine = MIN((_scrollPos + _h) / _lineHeight + 1, (uint32)_lines.size());
int y = _y + _yOff - (_scrollPos % _lineHeight);
for (int line = firstLine; line < lastLine; line++) {
Common::U32String str = _lines[line];
Common::U32String::const_iterator strLineItrBegin = _lines[line].begin();
Common::U32String::const_iterator strLineItrEnd = _lines[line].end();
Graphics::TextAlign align = Graphics::kTextAlignCenter;
ThemeEngine::WidgetStateInfo state = ThemeEngine::kStateEnabled;
if (strLineItrBegin != strLineItrEnd) {
switch (*strLineItrBegin) {
case 'C':
align = Graphics::kTextAlignCenter;
break;
case 'L':
align = Graphics::kTextAlignLeft;
break;
case 'R':
align = Graphics::kTextAlignRight;
break;
default:
error("Unknown scroller opcode '%c'", str[0]);
break;
}
switch (*(strLineItrBegin + 1)) {
case '0':
state = ThemeEngine::kStateEnabled;
break;
case '1':
state = ThemeEngine::kStateHighlight;
break;
case '2':
state = ThemeEngine::kStateDisabled;
break;
case '3':
warning("Need state for color 3");
// color = g_gui._shadowcolor;
break;
case '4':
warning("Need state for color 4");
// color = g_gui._bgcolor;
break;
default:
error("Unknown color type '%c'", str[1]);
}
strLineItrBegin += 2;
}
// Trim leading whitespaces if center mode is on
if (align == Graphics::kTextAlignCenter)
while (strLineItrBegin != strLineItrEnd && *strLineItrBegin == ' ')
strLineItrBegin++;
Common::U32String renderStr(strLineItrBegin, strLineItrEnd);
if (!renderStr.empty())
g_gui.theme()->drawText(Common::Rect(_x + _xOff, y, _x + _w - _xOff, y + g_gui.theme()->getFontHeight()),
renderStr, state, align, ThemeEngine::kTextInversionNone, 0, false,
ThemeEngine::kFontStyleBold, ThemeEngine::kFontColorNormal, true, _textDrawableArea);
y += _lineHeight;
}
}
void AboutDialog::handleTickle() {
const uint32 t = g_system->getMillis();
int scrollOffset = ((int)t - (int)_scrollTime) / kScrollMillisPerPixel;
if (_autoScroll && scrollOffset > 0) {
int modifiers = g_system->getEventManager()->getModifierState();
// Scroll faster when shift is pressed
if (modifiers & Common::KBD_SHIFT)
scrollOffset *= 4;
// Reverse scrolling when alt is pressed
if (modifiers & Common::KBD_ALT)
scrollOffset *= -1;
_scrollPos += scrollOffset;
_scrollTime = t;
if (_scrollPos < 0) {
_scrollPos = 0;
} else if ((uint32)_scrollPos > _lines.size() * _lineHeight) {
_scrollPos = 0;
_scrollTime += kScrollStartDelay;
}
drawDialog(kDrawLayerForeground);
}
}
void AboutDialog::handleMouseUp(int x, int y, int button, int clickCount) {
// Close upon any mouse click
close();
}
void AboutDialog::handleMouseWheel(int x, int y, int direction) {
const int stepping = 5 * _lineHeight * direction;
if (stepping == 0)
return;
_autoScroll = false;
int newScrollPos = _scrollPos + stepping;
if (newScrollPos < 0) {
_scrollPos = 0;
} else if ((uint32)newScrollPos < _lines.size() * _lineHeight) {
_scrollPos = newScrollPos;
}
drawDialog(kDrawLayerForeground);
}
void AboutDialog::handleKeyDown(Common::KeyState state) {
EEHandler eeHandler;
if (eeHandler.handleKeyDown(state)) {
reflowLayout();
return;
}
if (state.ascii)
_willClose = true;
}
void AboutDialog::handleKeyUp(Common::KeyState state) {
if (state.ascii && _willClose)
close();
}
void AboutDialog::reflowLayout() {
Dialog::reflowLayout();
int i;
const int screenW = g_system->getOverlayWidth();
const int screenH = g_system->getOverlayHeight();
_xOff = g_gui.xmlEval()->getVar("Globals.About.XOffset", 5);
_yOff = g_gui.xmlEval()->getVar("Globals.About.YOffset", 5);
int outerBorder = g_gui.xmlEval()->getVar("Globals.About.OuterBorder");
_w = screenW - 2 * outerBorder;
_h = screenH - 2 * outerBorder;
_lineHeight = g_gui.getFontHeight() + 3;
// Heuristic to compute 'optimal' dialog width
int maxW = _w - 2*_xOff;
_w = 0;
for (i = 0; i < ARRAYSIZE(credits); i++) {
int tmp = g_gui.getStringWidth(credits[i]) + 5;
if (_w < tmp && tmp <= maxW) {
_w = tmp;
}
}
_w += 2*_xOff;
// Center the dialog
_x = (screenW - _w) / 2;
_y = (screenH - _h) / 2;
}
////////////////////////////////
//// Here we go
////////////////////////////////
enum {
kDirUp, kDirLeft, kDirRight
};
enum {
kModeMenu, kModePlay, kModePause
};
enum { kPlComp, kPlHuman };
enum { kSndPoint, kSndHit, kSndLoss };
enum {
kSpL1, kSpL2, kSpL3,
kSpR1, kSpR2, kSpR3,
kSpB1, kSpB2, kSpB3, kSpB4,
kSp0, kSp1, kSp2, kSp3, kSp4,
kSp5, kSp6, kSp7, kSp8, kSp9,
kSpCode1,
kSpCode1h = kSpCode1 + 6,
kSpNum0 = kSpCode1h + 6,
kSpStar = kSpNum0 + 10,
kSpSpace,
kSpLast
};
class EE {
public:
EE();
~EE();
void run();
private:
Graphics::Surface _back;
int _hits;
bool _rmode; // animation after loosing
int _score[2];
bool _soundEnabled;
bool _shouldQuit;
int _rcnt; //last 20 cycles of round
int _winner; //who wins the point or serving
int _tvelx, _tvely;
int _x[2], _y[2], _px[2];
int _frame[2], _frameindex[2];
int _rebound[2];
int _bx, _by, _pbx, _pby, _tbx, _tby, _bvelx, _bvely;
int _serve, _server, _servevel, _compserve;
int _hitter, _starter, _sstage;
int _bytop;
int _rnd;
int _olx, _oly, _orx, _ory, _obx, _oby;
bool _air;
bool _oCoords;
int _mode;
int _keymove[2][3];
int _opts;
int _opt;
Graphics::AmigaFont _font;
Graphics::Surface _sp[kSpLast];
Graphics::PixelFormat _format;
int _windowX, _windowY, _windowW, _windowH;
float _scale;
bool _inited;
uint32 _colorBlack, _colorBlue, _colorOrange, _colorKey;
void cls(bool update = true);
void loadSounds();
void processKeyUp(Common::Event &e);
void processKeyDown(Common::Event &e);
void getPos();
void setwinner();
void resetpt();
void calcscore();
int destination(int pl, int destx, int tol);
bool moveball();
void docollisions();
void computer0();
void computer1();
void init();
void draw(int sn, int x, int y);
void doMenu(Common::Event &e);
void putshapes();
void game();
void playSound(int d);
void setupGraphics();
void genField();
};
bool EEHandler::handleKeyDown(Common::KeyState &state) {
if (state.ascii == 'v') {
EE *ee = new EE();
ee->run();
delete ee;
g_gui.redrawFull();
return true;
}
return false;
}
EE::EE() {
init();
_scale = 1.0;
_windowW = 320;
_windowH = 200;
_windowX = _windowY = 0;
_format = g_system->getOverlayFormat();
_colorBlack = _colorBlue = _colorOrange = _colorKey = 0;
}
EE::~EE() {
for (int i = 0; i < kSpLast; i++)
_sp[i].free();
}
void EE::cls(bool update) {
_back.fillRect(Common::Rect(0, 0, _windowW, _windowH), _colorBlack);
if (update)
g_system->copyRectToOverlay(_back.getPixels(), _back.pitch, _windowX, _windowY, _windowW, _windowH);
}
void EE::run() {
if (!_inited)
return;
_shouldQuit = false;
setupGraphics();
init();
while (!_shouldQuit) {
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_QUIT:
case Common::EVENT_RETURN_TO_LAUNCHER:
_shouldQuit = true;
break;
case Common::EVENT_SCREEN_CHANGED:
if (g_gui.checkScreenChange())
setupGraphics();
break;
case Common::EVENT_LBUTTONDOWN:
break;
case Common::EVENT_KEYDOWN:
processKeyDown(event);
break;
case Common::EVENT_KEYUP:
processKeyUp(event);
break;
default:
break;
}
}
game();
putshapes();
g_system->updateScreen();
g_system->delayMillis(10);
}
}
const int polecol[] = {0, 1, 2, 3, 3, 4, 6, 7, 9, 14};
const int jump[] = {-4, -4, -3, -3, -3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4};
void EE::loadSounds() {
#if 0
if ($.isEmptyObject(sounds)) {
for (i = 0; i < snNames.length; i++) {
s = snNames[i];
sounds[s] = new Audio();
sounds[s].src = 'sounds/' + s + '.wav';
}
}
#endif
}
void EE::processKeyUp(Common::Event &e) {
switch (e.kbd.keycode) {
case Common::KEYCODE_LEFT: // left
_keymove[1][kDirLeft] = 0;
break;
case Common::KEYCODE_RIGHT: // right
_keymove[1][kDirRight] = 0;
break;
case Common::KEYCODE_UP: // top
_keymove[1][kDirUp] = 0;
break;
case Common::KEYCODE_a: //a
_keymove[0][kDirLeft] = 0;
break;
case Common::KEYCODE_d: // d
_keymove[0][kDirRight] = 0;
break;
case Common::KEYCODE_w: // w
_keymove[0][kDirUp] = 0;
break;
default:
break;
}
}
void EE::processKeyDown(Common::Event &e) {
if (_mode == kModeMenu) {
doMenu(e);
return;
}
switch (e.kbd.keycode) {
case Common::KEYCODE_LEFT: // left
_keymove[1][kDirLeft] = -2;
break;
case Common::KEYCODE_RIGHT: // right
_keymove[1][kDirRight] = 2;
break;
case Common::KEYCODE_UP: // top
_keymove[1][kDirUp]= 1;
break;
case Common::KEYCODE_a: //a
_keymove[0][kDirLeft] = -2;
break;
case Common::KEYCODE_d: // d
_keymove[0][kDirRight] = 2;
break;
case Common::KEYCODE_w: // w
_keymove[0][kDirUp] = 1;
break;
case Common::KEYCODE_ESCAPE:
_mode = kModeMenu;
break;
case Common::KEYCODE_p: // p
_keymove[0][kDirRight] = _keymove[0][kDirLeft] = _keymove[0][kDirUp] = 0;
_mode = (_mode == kModePlay) ? kModePause : kModePlay;
break;
case Common::KEYCODE_r: // r
if (_mode == kModePlay)
init();
break;
case Common::KEYCODE_s: // s
if (!_soundEnabled)
loadSounds();
_soundEnabled = !_soundEnabled;
break;
default:
break;
}
}
void EE::getPos() {
for (int i = 0; i < 2; i++) {
if (_keymove[i][kDirUp] && _frameindex[i] == -1)
_frameindex[i] = 0;
int velx = _keymove[i][kDirLeft] + _keymove[i][kDirRight];
int tx = _x[i] + velx;
int bnd = 3 + i * 155;
if (velx > 0) {
if (tx < bnd + 119)
_x[i] = tx;
else
_x[i] = bnd + 119;
} else if (tx > bnd) {
_x[i] = tx;
} else {
_x[i] = bnd;
}
if (_frameindex[i] == -2) {
_y[i] = 173;
_frame[i] = 0;
_frameindex[i] = -1;
}
if (_frameindex[i] == -1) {
if (velx != 0) {
if (abs(_px[i] - _x[i]) > 4) {
_frame[i] ^= 1;
_px[i] = _x[i];
}
} else {
_frame[i] = 0;
}
} else {
_frame[i] = 2 + (_frameindex[i] > 18);
if (_frameindex[i] == 19)
_y[i] -= 4;
_y[i] += jump[_frameindex[i]++];
if (_frameindex[i] > 37)
_frameindex[i] = -2;
}
}
}
void EE::setwinner() {
if (_hits > 2) {
_winner = 1 - _hitter;
} else {
_winner = _tbx < 150 ? 1 : 0;
}
_tvely = abs(_bvely) >> 3;
_tvelx = abs(_bvelx) >> 3;
}
void EE::resetpt() {
_keymove[0][kDirUp] = 0;
_keymove[1][kDirUp] = 0;
getPos();
if (abs(_bvelx) > _tvelx) {
if (_bvelx < 0)
_bvelx = -_tvelx;
else
_bvelx = _tvelx;
}
if (abs(_bvely) > _tvely) {
if (_bvely < 0)
_bvely = -_tvely;
else
_bvely = _tvely;
}
docollisions();
moveball();
putshapes();
}
void EE::calcscore() {
if (_winner == _server) {
if (_soundEnabled && _mode != kModeMenu) {
playSound(kSndPoint);
}
_score[_winner]++;
if (_score[_winner] > 14 && _score[_winner] - _score[1 - _winner] > 1) {
init();
return;
}
} else {
if (_soundEnabled && _mode != kModeMenu) {
playSound(kSndLoss);
}
_server = _winner;
}
_bx = (_tbx = _pbx = (64 + _winner * 165)) << 6;
_by = (_tby = _pby = 135) << 6;
_bvelx = _bvely = _hits = _rebound[0] = _rebound[1] = 0;
_serve = _servevel = 1;
_hitter = 2;
_compserve = abs(_rnd) % 5;
if (_score[_server] == 14)
_compserve = 5;
_sstage = 0;
}
int EE::destination(int pl, int destx, int tol) {
int xp = _x[pl];
if (abs(xp - destx) < tol) {
_keymove[pl][kDirLeft] = 0;
_keymove[pl][kDirRight] = 0;
return 1;
}
if (xp < destx) {
_keymove[pl][kDirLeft] = 0;
_keymove[pl][kDirRight] = 2;
} else {
_keymove[pl][kDirLeft] = -2;
_keymove[pl][kDirRight] = 0;
}
return 0;
}
int reset = false;
bool EE::moveball() {
if (!reset) {
_bx = 4096; _by = 8640; _bvelx = 125; _bvely = -259;
reset = true;
}
int rbvelx = _bvelx;
int rbvely = _bvely;
if (rbvelx > 319) rbvelx = 319;
if (rbvelx < -319) rbvelx = -319;
if (rbvely > 319) rbvely = 319;
if (rbvely < -319) rbvely = -319;
_pbx = _tbx;
_pby = _tby;
_bx += rbvelx;
_by += rbvely;
if (_bx < 320) {
_bx = 320;
rbvelx = -rbvelx;
rbvelx -= rbvelx >> 4;
rbvely -= rbvely >> 4;
if (_hitter == 1) {
_hitter = 2;
_hits = 0;
}
}
if (_bx > 18112) {
_bx = 18112;
rbvelx = -rbvelx;
rbvelx -= rbvelx >> 4;
rbvely -= rbvely >> 4;
if (_hitter == 0) {
_hitter = 2;
_hits = 0;
}
}
if (_by < 832) {
_by = 832;
rbvely = -rbvely;
rbvelx -= rbvelx >> 4;
rbvely -= rbvely >> 4;
}
bool hitfloor;
if (_by > 11392) {
_by = 11392;
rbvely = -rbvely;
hitfloor = false;
} else {
hitfloor = true;
}
//if (rbvely > 0) // Checked with original, this is how it is
// rbvely += 1;
//else
rbvely += 1;
_tbx = _bx >> 6;
_tby = _by >> 6;
_bvelx = rbvelx;
_bvely = rbvely;
return hitfloor;
}
void EE::docollisions() {
for (int i = 0; i < 2; i++) {
int dx = _tbx - _x[i] - i * 7;
int dy = (_tby - _y[i]) >> 1;
int dist = (dx >> 2) * dx + dy * dy;
if (dist < 110) {
int rndoff = 8 - (_rnd & 15);
if (_frameindex[i] > -1)
_bvely = -abs(_bvely) + (jump[_frameindex[i]] << (3 << _servevel));
else
_bvely = -abs(_bvely);
_bvely += rndoff;
_bvelx += dx * abs(dx) + ((_keymove[i][kDirRight] + _keymove[i][kDirLeft]) << (4 + _servevel)) + rndoff;
if (!_rebound[i]) {
if (_mode != kModeMenu) {
if (_soundEnabled && _mode != kModeMenu) {
playSound(kSndHit);
}
}
_bytop = 200;
_serve = 0;
_rebound[i] = 1;
if (_hitter != i) {
_hitter = i;
_hits = 0;
} else {
_hits++;
}
}
} else if (_rebound[i]) {
_rebound[i] = _servevel = 0;
}
}
int i = 1;
if (_tby > 91) {
if (_pbx < 128 && _tbx > 127) {
_bvelx = -abs(_bvelx) >> 1;
_bx = 127 * 64;
i = 0;
} else if (_pbx > 159 && _tbx < 160) {
_bvelx = abs(_bvelx) >> 1;
_bx = 160 * 64;
i = 0;
}
}
if (i && _tby > 81 && _tbx > 127 && _tbx < 160) {
if (_tby > 91) {
if (_tbx < 148)
_bvelx = -abs(_bvelx);
else
_bvelx = abs(_bvelx);
} else if ((_tbx > 147 && 161 - _tbx >= polecol[91 - _tby]) ||
(_tbx < 148 && _tbx - 133 >= polecol[91 - _tby])) {
if (_bvely > 0) {
int dx = _tbx - 145;
if (dx < -5) _bvelx = -abs(_bvelx);
if (dx > 5) _bvelx = abs(_bvelx);
_bvely = -abs(_bvely);
}
if (abs(_bvelx) > 32) _bvelx = _bvelx >> 1;
if (abs(_bvely) > 32) _bvely = _bvely >> 1;
}
}
}
void EE::computer0() {
_keymove[0][kDirUp] = 0;
if (_tby < _bytop) _bytop = _tby;
int rndoff = 5 - _rnd % 10;
int dest = 0;
if (_serve && ((_server & 1) == 0)) {
switch (_compserve) {
case 0:
dest = destination(0, 55, 2);
break;
case 1:
dest = destination(0, 84, 2);
break;
case 2:
dest = destination(0, 80, 2);
break;
case 3:
if (_sstage == 0) {
if ((dest = destination(0, 44, 2)))
_sstage = 1;
} else {
destination(0, 58, 2);
dest = 1;
}
break;
case 4:
if (_sstage == 0) {
if ((dest = destination(0, 90, 2)))
_sstage = 1;
} else {
destination(0, 58, 2);
dest = 1;
}
break;
case 5:
if (_sstage == 0) {
if (destination(0, 3, 2))
_sstage = 1;
dest = 0;
} else {
destination(0, 8 + _sstage++, 1);
dest = 1;
}
break;
default:
break;
}
_keymove[0][kDirUp] = dest;
} else if (_bvely > 0 && _tbx < 140) {
int ystep, destx;
if (_bvely >> 6 == 0)
ystep = 0;
else
ystep = (140 - _tby) / (_bvely >> 6);
if (ystep < 1 || (_bvelx >> 6) == 0)
destx = _tbx;
else
destx = _tbx + (_bvelx >> 6) * ystep - 4;
int dx = _x[0] - _tbx;
if (abs(_bvelx) < 128 && _bytop < 75) {
if ((_tby < 158) ^ (_bvelx < 0))
destination(0, _tbx - 15, 3);
else
destination(0, _tbx + 15, 3);
} else {
if (_tby > 130) {
if (abs(dx) > 6 && abs(_bvelx) < 1024) {
destination(0, _tbx - ((_x[0] - 60) >> 3), 3);
} else {
destination(0, _tbx + rndoff + ((_x[0] - 60) >> 3), 10);
_keymove[0][kDirUp] = _x[0] < 105 && (_hitter != 0 || _hits < 2);
}
} else {
if (destx < 3)
destx = 6 - destx;
if (destx > 123)
destx = 246 - destx;
destination(0, destx + rndoff, 3);
}
}
} else
destination(0, 56, 8);
}
void EE::computer1() {
_keymove[1][kDirUp] = 0;
if (_tby < _bytop) _bytop = _tby;
int rndoff = 5 - _rnd % 10;
if (_serve && ((_server & 1) == 1)) {
int dest = 0;
switch (_compserve) {
case 0:
dest = destination(1, 232, 2);
break;
case 1:
dest = destination(1, 202, 2);
break;
case 2:
dest = destination(1, 208, 2);
break;
case 3:
if (_sstage == 0) {
if ((dest = destination(1, 250, 2)))
_sstage = 1;
} else {
destination(1, 220, 2);
dest = 1;
}
break;
case 4:
if (_sstage == 0) {
if ((dest = destination(1, 190, 2)))
_sstage = 1;
} else {
destination(1, 230, 2);
dest = 1;
}
break;
case 5:
if (_sstage == 0) {
if (destination(1, 277, 2))
_sstage = 1;
dest = 0;
} else {
destination(1, 272 - _sstage++, 1);
dest = 1;
}
break;
default:
break;
}
_keymove[1][kDirUp] = dest;
} else if (_bvely > 0 && _tbx > 125) {
int ystep, destx;
if (_bvely >> 6 == 0)
ystep = 0;
else
ystep = (140 - _tby) / (_bvely >> 6);
if (ystep < 1 || (_bvelx >> 6) == 0)
destx = _tbx;
else
destx = _tbx + (_bvelx >> 6) * ystep - 4;
int dx = _x[1] - _tbx;
if (abs(_bvelx) < 128 && _bytop < 75) {
if ((_tby < 158) ^ (_bvelx < 0))
destination(1, _tbx + 15, 3);
else
destination(1, _tbx - 15, 3);
} else {
if (_tby > 130) {
if (abs(dx) > 6 && abs(_bvelx) < 1024)
destination(1, _tbx + ((_x[1] - 218) >> 3), 3);
else {
destination(1, _tbx - rndoff - ((_x[1] - 218) >> 3), 10);
_keymove[1][kDirUp] = _x[1] > 175 && (_hitter != 1 || _hits < 2);
}
} else {
if (destx < 158)
destx = 316 - destx;
if (destx > 277)
destx = 554 - destx;
destination(1, destx - rndoff, 3);
}
}
} else
destination(1, 211, 8);
}
void EE::init() {
_rnd = 0;
_starter = _winner = _hits = 0;
_bvelx = _bvely = 0;
_tbx = 200; _tby = 20;
_bytop = 200;
_x[0] = 64; _x[1] = 226;
_air = false;
_rcnt = 0;
_tvelx = _tvely = 0;
for (int i = 0; i < 2; i++) {
_px[i] = _x[i];
_y[i] = 173;
_frameindex[i] = -1;
_rebound[i] = 0;
_score[i] = 0;
_keymove[i][kDirRight] = _keymove[i][kDirLeft] = _keymove[i][kDirUp] = 0;
_frame[i] = 0;
}
_oCoords = false;
_opts = 3;
_opt = 0;
_mode = kModeMenu;
_soundEnabled = false;
_olx = _oly = _orx = _ory = _obx = _oby = 0;
_compserve = 0;
_sstage = 0;
_tbx = 64 + _starter * 165;
_pbx = _tbx;
_bx = _tbx << 6;
_tby = 135;
_pby = 135;
_by = _tby << 6;
_server = 2 + _starter;
_hitter = 2;
_serve = _servevel = 1;
_rmode = false;
_shouldQuit = false;
_inited = true;
}
void EE::draw(int sn, int x1, int y1) {
int x = x1 * _scale;
int y = y1 * _scale;
_back.copyRectToSurfaceWithKey(_sp[sn].getPixels(), _sp[sn].pitch, x, y, _sp[sn].w, _sp[sn].h, _colorKey);
g_system->copyRectToOverlay(_back.getBasePtr(x, y), _back.pitch, _windowX + x, _windowY + y, _sp[sn].w, _sp[sn].h);
}
void EE::putshapes() {
int sprite;
cls(false);
if (_oCoords) {
int obx = _obx * _scale, oby = _oby * _scale;
int olx = _olx * _scale, oly = _oly * _scale;
int orx = _orx * _scale, ory = _ory * _scale;
g_system->copyRectToOverlay(_back.getBasePtr(obx, oby), _back.pitch, _windowX + obx, _windowY + oby, _sp[kSpB1].w, _sp[kSpB1].h);
g_system->copyRectToOverlay(_back.getBasePtr(olx, oly), _back.pitch, _windowX + olx, _windowY + oly, _sp[kSpL1].w, _sp[kSpL1].h);
g_system->copyRectToOverlay(_back.getBasePtr(orx, ory), _back.pitch, _windowX + orx, _windowY + ory, _sp[kSpR1].w, _sp[kSpR1].h);
}
sprite = kSpB1 + (_tbx / 16) % 4;
draw(sprite, _tbx, _tby);
_obx = _tbx;
_oby = _tby;
if (_y[0] < 173) {
sprite = kSpL3;
} else {
if ((_x[0] / 8) % 2)
sprite = kSpL1;
else
sprite = kSpL2;
}
draw(sprite, _x[0], _y[0]);
_olx = _x[0];
_oly = _y[0];
if (_y[1] < 173) {
sprite = kSpR3;
} else {
if ((_x[1] / 8) % 2)
sprite = kSpR1;
else
sprite = kSpR2;
}
draw(sprite, _x[1], _y[1]);
_orx = _x[1];
_ory = _y[1];
_oCoords = true;
const int frames[] = {
153, 106, 160, 200,
4, 13, 6, 200,
310, 13, 312, 200,
4, 13, 312, 14,
4, 199, 312, 200,
154, 107, 159, 199
};
uint32 color1 = _back.format.RGBToColor(5 * 16, 7 * 16, 8 * 16);
uint32 color2 = _back.format.RGBToColor(6 * 16, 8 * 16, 12 * 16);
const int *ptr = frames;
for (int i = 0; i < 6; i++, ptr += 4) {
Common::Rect r(ptr[0] * _scale, ptr[1] * _scale, ptr[2] * _scale, ptr[3] * _scale);
_back.fillRect(r, (i < 5 ? color1 : color2));
g_system->copyRectToOverlay(_back.getBasePtr(r.left, r.top), _back.pitch, _windowX + r.left, _windowY + r.top, r.width(), r.height());
}
for (int i = 0; i < 2; i++) {
int startx = i ? 264 : 32;
draw(kSpNum0 + _score[i] / 10, startx, 1);
startx += 8;
draw(kSpNum0 + _score[i] % 10, startx, 1);
startx += 8;
draw(_server == i ? kSpStar : kSpSpace, startx, 1);
}
for (int i = 0; i < 6; i++) {
int sx = i == 0 ? 92 : 80;
int sy = i == 0 ? 2 : 20;
int code = i == 0 ? kSpCode1 : i - 1 == _opt ? kSpCode1h + i : kSpCode1 + i;
draw(code, sx, sy + i * 10);
if (_mode != kModeMenu)
break;
}
}
void EE::doMenu(Common::Event &e) {
switch (e.kbd.keycode) {
case Common::KEYCODE_UP:
_opt--;
if (_opt < 0)
_opt = 4;
break;
case Common::KEYCODE_DOWN:
_opt = (_opt + 1) % 5;
break;
case Common::KEYCODE_RETURN:
if (_opt < 4) {
_opts = _opt;
_mode = kModePlay;
cls();
} else {
_shouldQuit = true;
}
break;
case Common::KEYCODE_ESCAPE:
_shouldQuit = true;
break;
default:
break;
}
}
void EE::game() {
if (_mode == kModePlay || _mode == kModeMenu) {
_rnd = (_rnd * 5 + 1) % 32767;
if (_opts & 1)
computer0();
if (_opts & 2)
computer1();
if (_rmode) {
if (_rcnt-- > 0 || _frameindex[0] != -1 || _frameindex[1] != -1) {
resetpt();
} else {
_rmode = false;
calcscore();
}
return;
}
getPos();
if (_serve) {
docollisions();
_air = true;
} else {
if (_air) {
docollisions();
_air = moveball();
}
}
if (!_air || _hits > 2) {
setwinner();
_rmode = true;
_rcnt = 20;
}
}
}
void EE::playSound(int sound) {
// FIXME
}
static const uint32 ball[21] = {
0x0000fd00, 0x00074ac0, 0x000afde0, 0x001703e8, 0x0038f7d8, 0x0056f73c,
0x00aef6fc, 0x00cf79fb, 0x01afbbf7, 0x01b7bdcd, 0x0077dd3a, 0x013beef3,
0x01bbef6a, 0x015df78b, 0x00defbea, 0x00ef7dd8, 0x00779eb4, 0x0029ef68,
0x001ef4a0, 0x000673c0, 0x0000ae00
};
static const uint32 head[38] = {
0xbb400000, 0x0000000f, 0xea650000, 0x000003af, 0xfaaa6000, 0x00000e7f,
0xfea5a400, 0x03ff2af0, 0x3eaa6900, 0x3ea969c0, 0x3ea99940, 0xeaaa69c8,
0x3a669540, 0xea995540, 0x5a555550, 0xa9aa999a, 0x65a65550, 0x2aa69a5a,
0x59555650, 0x02aaaa55, 0x6aa66550, 0x000aa950, 0x6a569950, 0x000a9500,
0xa9595580, 0x00015001, 0xa9a55540, 0x00000015, 0xa9a9a500, 0x0000015a,
0x59695800, 0x00001659, 0x65659000, 0x00000a96, 0xa65a8000, 0x000002aa,
0xa6500000, 0x0000002a
};
static const uint32 legs[42] = {
0xa0000000, 0x00000000, 0x80000000, 0x00000002, 0x80000000, 0x00000002, 0xa0000000,
0x00000000, 0x50000000, 0x00000000, 0xf0000000, 0x00000003, 0xfc000000, 0x000003ff,
0xa0000000, 0x00000002, 0x0a000000, 0x0000000a, 0x02400000, 0x00000028, 0x00700000,
0x00000028, 0x00fc0000, 0x00000014, 0x03c00000, 0x000000fc, 0x0f000000, 0x0000ffff,
0xa0000000, 0x00000000, 0x80000000, 0x00000002, 0xa8000000, 0x00000002, 0x29c00000,
0x00000000, 0x01f00000, 0x00000000, 0x0fc00000, 0x00000000, 0xfc000000, 0x00000000
};
static const int spcolors[10 * 3] = {
0, 0, 0, 8, 15, 8, 6, 12, 6, 12, 13, 12,
0, 0, 0, 15, 8, 8, 12, 6, 6, 13, 12, 12,
0, 0, 0, 12, 12, 12
};
const char *codes =
"Dvhgkm#Ztrsm|ffrs(#$%&'#$%&O}pes&}{1$M{tiq$%&'#$M{tiq${y5(Fsrv||hv%&'#$"
"Hutxxxjx'~v2%N|udr%&'#Gtsw}wiw&}{1$Hutxxxjx'#$%&'(#$%W|qw$%&'(#$%&'";
void EE::setupGraphics() {
// Determine scale factor
float scaleX = g_system->getOverlayWidth() * 0.9 / 320;
float scaleY = g_system->getOverlayHeight() * 0.9 / 200;
_scale = MIN(scaleX, scaleY);
_windowW = 320 * _scale;
_windowH = 200 * _scale;
_windowX = (g_system->getOverlayWidth() > 320) ? (g_system->getOverlayWidth() - _windowW) / 2 : 0;
_windowY = (g_system->getOverlayHeight() > 200) ? (g_system->getOverlayHeight() - _windowH) / 2 : 0;
_format = g_system->getOverlayFormat();
_back.create(_windowW, _windowH, _format);
_colorBlack = _format.RGBToColor( 0 * 16, 0 * 16, 0 * 16);
_colorBlue = _format.RGBToColor( 5 * 16, 7 * 16, 8 * 16);
_colorOrange = _format.RGBToColor(15 * 16, 7 * 16, 8 * 16);
_colorKey = _colorBlack;
cls();
uint32 palette[12];
for (int i = 0; i < 10 * 3; i += 3)
palette[i / 3] = _back.format.RGBToColor(spcolors[i] * 16, spcolors[i + 1] * 16, spcolors[i + 2] * 16);
Graphics::Surface tmp;
int w = 25, h = 21;
tmp.create(w, h, g_system->getOverlayFormat());
for (int s = 0; s < 4; s++) {
int posy = 0, dy = 1;
if (s & 2) { posy = 20; dy = -1; }
for (int y = 0; y < h; y++, posy += dy) {
uint32 pixels = ball[y];
int posx = 0, dx = 1;
if (s & 1) { posx = 24; dx = -1; }
for (int x = 0; x < w; x++, posx += dx) {
int color = pixels & 1;
pixels >>= 1;
tmp.setPixel(posx, posy, palette[color + 8]);
}
}
Graphics::Surface *tmp2 = tmp.scale(w * _scale, h * _scale);
_sp[kSpB1 + s].copyFrom(*tmp2);
tmp2->free();
delete tmp2;
}
tmp.free();
w = 32;
h = 26;
tmp.create(w, h, g_system->getOverlayFormat());
for (int s = 0; s < 6; s++) {
for (int y = 0; y < h; y++) {
const uint32 *ptr = (y < 19) ? &head[y * 2] : &legs[(y - 19 + (s % 3) * 7) * 2];
uint32 pixels = *ptr++;
int posx = 0, dx = 1;
if (s > 2) { posx = 31; dx = -1; }
for (int x = 0; x < w; x++, posx += dx) {
int color = pixels & 3;
pixels >>= 2;
if (x == 15)
pixels = *ptr;
tmp.setPixel(posx, y, palette[color + 4 * (s / 3)]);
}
}
Graphics::Surface *tmp2 = tmp.scale(w * _scale, h * _scale);
_sp[kSpL1 + s].copyFrom(*tmp2);
tmp2->free();
delete tmp2;
}
tmp.free();
w = 23 * 8;
h = 10;
tmp.create(w, h, g_system->getOverlayFormat());
for (int hl = 0; hl < 2; hl++) {
for (int i = 0; i < 6; i++) {
tmp.fillRect(Common::Rect(0, 0, w, h), hl == 1 ? _colorBlue : _colorKey);
char buf[100];
int c;
for (c = 0; c < 23; c++)
buf[c] = codes[c + 23 * i] - 3 - c % 6;
buf[c] = 0;
int c1 = i == 0 ? _colorOrange : hl == 1 ? _colorKey : _colorBlue;
_font.drawString(&tmp, buf, 0, 1, w, c1, Graphics::kTextAlignLeft, 0, false);
Graphics::Surface *tmp2 = tmp.scale(w * _scale, h * _scale, true);
_sp[kSpCode1 + hl * 6 + i].copyFrom(*tmp2);
tmp2->free();
delete tmp2;
}
}
tmp.free();
w = 8;
h = 10;
tmp.create(w, h, g_system->getOverlayFormat());
for (int i = 0; i < 12; i++) {
tmp.fillRect(Common::Rect(0, 0, w, h), _colorKey);
char buf[2];
buf[0] = i == 10 ? '*' : i == 11 ? ' ' : '0' + i;
buf[1] = 0;
int c = i > 9 ? _colorBlue : _colorOrange;
_font.drawString(&tmp, buf, 0, 1, w, c, Graphics::kTextAlignLeft, 0, false);
Graphics::Surface *tmp2 = tmp.scale(w * _scale, h * _scale, true);
_sp[kSpNum0 + i].copyFrom(*tmp2);
tmp2->free();
delete tmp2;
}
tmp.free();
}
} // End of namespace GUI