1943 lines
59 KiB
C++
1943 lines
59 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 "ultima/ultima4/controllers/intro_controller.h"
|
|
#include "ultima/ultima4/controllers/menu_controller.h"
|
|
#include "ultima/ultima4/controllers/read_string_controller.h"
|
|
#include "ultima/ultima4/controllers/read_choice_controller.h"
|
|
#include "ultima/ultima4/game/player.h"
|
|
#include "ultima/ultima4/views/menu.h"
|
|
#include "ultima/ultima4/core/config.h"
|
|
#include "ultima/ultima4/core/utils.h"
|
|
#include "ultima/ultima4/core/settings.h"
|
|
#include "ultima/ultima4/events/event_handler.h"
|
|
#include "ultima/ultima4/filesys/savegame.h"
|
|
#include "ultima/ultima4/filesys/u4file.h"
|
|
#include "ultima/ultima4/gfx/imagemgr.h"
|
|
#include "ultima/ultima4/gfx/screen.h"
|
|
#include "ultima/ultima4/map/mapmgr.h"
|
|
#include "ultima/ultima4/map/shrine.h"
|
|
#include "ultima/ultima4/map/tileset.h"
|
|
#include "ultima/ultima4/map/tilemap.h"
|
|
#include "ultima/ultima4/sound/music.h"
|
|
#include "ultima/ultima4/sound/sound.h"
|
|
#include "ultima/ultima4/ultima4.h"
|
|
#include "common/savefile.h"
|
|
#include "common/system.h"
|
|
|
|
namespace Ultima {
|
|
namespace Ultima4 {
|
|
|
|
IntroController *g_intro;
|
|
|
|
#define INTRO_MAP_HEIGHT 5
|
|
#define INTRO_MAP_WIDTH 19
|
|
#define INTRO_TEXT_X 0
|
|
#define INTRO_TEXT_Y 19
|
|
#define INTRO_TEXT_WIDTH 40
|
|
#define INTRO_TEXT_HEIGHT 6
|
|
|
|
#define GYP_PLACES_FIRST 0
|
|
#define GYP_PLACES_TWOMORE 1
|
|
#define GYP_PLACES_LAST 2
|
|
#define GYP_UPON_TABLE 3
|
|
#define GYP_SEGUE1 13
|
|
#define GYP_SEGUE2 14
|
|
|
|
#define INTRO_SCRIPT_TABLE_SIZE 548
|
|
#define INTRO_BASETILE_TABLE_SIZE 15
|
|
#define BEASTIE1_FRAMES 0x80
|
|
#define BEASTIE2_FRAMES 0x40
|
|
|
|
class IntroObjectState {
|
|
public:
|
|
IntroObjectState() : x(0), y(0), tile(0) {}
|
|
int x, y;
|
|
MapTile tile; /* base tile + tile frame */
|
|
};
|
|
|
|
void IntroController::AnimElement::shufflePlotData() {
|
|
for (int idx = 0; idx < ((int)_plotData.size() - 1); ++idx) {
|
|
// Pick a random element from the remainder of the plot data
|
|
int srcIndex = idx + g_ultima->getRandomNumber(_plotData.size() - idx - 1);
|
|
if (srcIndex != idx)
|
|
SWAP(_plotData[srcIndex], _plotData[idx]);
|
|
}
|
|
}
|
|
|
|
IntroBinData::IntroBinData() :
|
|
_sigData(nullptr),
|
|
_scriptTable(nullptr),
|
|
_baseTileTable(nullptr),
|
|
_beastie1FrameTable(nullptr),
|
|
_beastie2FrameTable(nullptr) {
|
|
}
|
|
|
|
IntroBinData::~IntroBinData() {
|
|
if (_sigData)
|
|
delete [] _sigData;
|
|
if (_scriptTable)
|
|
delete [] _scriptTable;
|
|
if (_baseTileTable)
|
|
delete [] _baseTileTable;
|
|
if (_beastie1FrameTable)
|
|
delete [] _beastie1FrameTable;
|
|
if (_beastie2FrameTable)
|
|
delete [] _beastie2FrameTable;
|
|
|
|
_introQuestions.clear();
|
|
_introText.clear();
|
|
_introGypsy.clear();
|
|
}
|
|
|
|
void IntroBinData::openFile(Common::File &f, const Common::String &name) {
|
|
if (!f.open(Common::Path(Common::String::format("data/intro/%s.dat", name.c_str()))))
|
|
error("Could not load '%s'", name.c_str());
|
|
}
|
|
|
|
bool IntroBinData::load() {
|
|
int i;
|
|
|
|
_introQuestions = u4read_stringtable("intro_questions");
|
|
_introText = u4read_stringtable("intro_text");
|
|
_introGypsy = u4read_stringtable("intro_gypsy");
|
|
|
|
// Clean up stray newlines at end of strings
|
|
for (i = 0; i < 15; i++)
|
|
trim(_introGypsy[i]);
|
|
|
|
if (_sigData)
|
|
delete _sigData;
|
|
_sigData = new byte[533];
|
|
|
|
Common::File f;
|
|
openFile(f, "intro_sig");
|
|
f.read(_sigData, 533);
|
|
f.close();
|
|
|
|
openFile(f, "intro_map");
|
|
_introMap.clear();
|
|
_introMap.resize(INTRO_MAP_WIDTH * INTRO_MAP_HEIGHT);
|
|
for (i = 0; i < INTRO_MAP_HEIGHT * INTRO_MAP_WIDTH; i++)
|
|
_introMap[i] = g_tileMaps->get("base")->translate(f.readByte());
|
|
f.close();
|
|
|
|
openFile(f, "intro_script");
|
|
_scriptTable = new byte[INTRO_SCRIPT_TABLE_SIZE];
|
|
for (i = 0; i < INTRO_SCRIPT_TABLE_SIZE; i++)
|
|
_scriptTable[i] = f.readByte();
|
|
f.close();
|
|
|
|
openFile(f, "intro_base_tile");
|
|
_baseTileTable = new Tile*[INTRO_BASETILE_TABLE_SIZE];
|
|
for (i = 0; i < INTRO_BASETILE_TABLE_SIZE; i++) {
|
|
MapTile tile = g_tileMaps->get("base")->translate(f.readByte());
|
|
_baseTileTable[i] = g_tileSets->get("base")->get(tile._id);
|
|
}
|
|
f.close();
|
|
|
|
/* --------------------------
|
|
load beastie frame table 1
|
|
-------------------------- */
|
|
openFile(f, "intro_beastie1");
|
|
_beastie1FrameTable = new byte[BEASTIE1_FRAMES];
|
|
for (i = 0; i < BEASTIE1_FRAMES; i++) {
|
|
_beastie1FrameTable[i] = f.readByte();
|
|
}
|
|
f.close();
|
|
|
|
/* --------------------------
|
|
load beastie frame table 2
|
|
-------------------------- */
|
|
openFile(f, "intro_beastie2");
|
|
_beastie2FrameTable = new byte[BEASTIE2_FRAMES];
|
|
for (i = 0; i < BEASTIE2_FRAMES; i++) {
|
|
_beastie2FrameTable[i] = f.readByte();
|
|
}
|
|
f.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
IntroController::IntroController() : Controller(1),
|
|
_backgroundArea(),
|
|
_menuArea(1 * CHAR_WIDTH, 13 * CHAR_HEIGHT, 38, 11),
|
|
_extendedMenuArea(2 * CHAR_WIDTH, 10 * CHAR_HEIGHT, 36, 13),
|
|
_questionArea(INTRO_TEXT_X * CHAR_WIDTH, INTRO_TEXT_Y * CHAR_HEIGHT, INTRO_TEXT_WIDTH, INTRO_TEXT_HEIGHT),
|
|
_mapArea(BORDER_WIDTH, (TILE_HEIGHT * 6) + BORDER_HEIGHT, INTRO_MAP_WIDTH, INTRO_MAP_HEIGHT, "base"),
|
|
_binData(nullptr), _mode(INTRO_TITLES), _answerInd(0), _questionRound(0),
|
|
_beastie1Cycle(0), _beastie2Cycle(0), _beastieOffset(0),
|
|
_beastiesVisible(false), _sleepCycles(0), _scrPos(0),
|
|
_objectStateTable(nullptr), _justInitiatedNewGame(false),
|
|
_titles(), // element list
|
|
_title(_titles.begin()), // element iterator
|
|
_transparentIndex(13), // palette index for transparency
|
|
_transparentColor(), // palette color for transparency
|
|
_bSkipTitles(false),
|
|
_useProfile(false) {
|
|
Common::fill(&_questionTree[0], &_questionTree[15], -1);
|
|
|
|
// Setup a separate image surface for rendering the animated map on
|
|
_mapScreen = Image::create(g_screen->w, g_screen->h, g_screen->format);
|
|
_mapArea.setDest(_mapScreen);
|
|
|
|
// initialize menus
|
|
_confMenu.setTitle("XU4 Configuration:", 0, 0);
|
|
_confMenu.add(MI_CONF_VIDEO, "\010 Video Options", 2, 2,/*'v'*/ 2);
|
|
_confMenu.add(MI_CONF_SOUND, "\010 Sound Options", 2, 3,/*'s'*/ 2);
|
|
_confMenu.add(MI_CONF_INPUT, "\010 Input Options", 2, 4,/*'i'*/ 2);
|
|
_confMenu.add(MI_CONF_SPEED, "\010 Speed Options", 2, 5,/*'p'*/ 3);
|
|
_confMenu.add(MI_CONF_01, new BoolMenuItem("Game Enhancements %s", 2, 7,/*'e'*/ 5, &_settingsChanged._enhancements));
|
|
_confMenu.add(MI_CONF_GAMEPLAY, "\010 Enhanced Gameplay Options", 2, 9,/*'g'*/ 11);
|
|
_confMenu.add(MI_CONF_INTERFACE, "\010 Enhanced Interface Options", 2, 10,/*'n'*/ 12);
|
|
_confMenu.add(CANCEL, "\017 Main Menu", 2, 12,/*'m'*/ 2);
|
|
_confMenu.addShortcutKey(CANCEL, ' ');
|
|
_confMenu.setClosesMenu(CANCEL);
|
|
|
|
/* set the default visibility of the two enhancement menus */
|
|
_confMenu.getItemById(MI_CONF_GAMEPLAY)->setVisible(settings._enhancements);
|
|
_confMenu.getItemById(MI_CONF_INTERFACE)->setVisible(settings._enhancements);
|
|
|
|
_videoMenu.setTitle("Video Options:", 0, 0);
|
|
_videoMenu.add(MI_VIDEO_CONF_GFX, "\010 Game Graphics Options", 2, 2,/*'g'*/ 2);
|
|
_videoMenu.add(MI_VIDEO_08, new IntMenuItem("Gamma %s", 2, 4,/*'a'*/ 1, &_settingsChanged._gamma, 50, 150, 10, MENU_OUTPUT_GAMMA));
|
|
_videoMenu.add(USE_SETTINGS, "\010 Use These Settings", 2, 11,/*'u'*/ 2);
|
|
_videoMenu.add(CANCEL, "\010 Cancel", 2, 12,/*'c'*/ 2);
|
|
_videoMenu.addShortcutKey(CANCEL, ' ');
|
|
_videoMenu.setClosesMenu(USE_SETTINGS);
|
|
_videoMenu.setClosesMenu(CANCEL);
|
|
|
|
_gfxMenu.setTitle("Game Graphics Options", 0, 0);
|
|
_gfxMenu.add(MI_GFX_SCHEME, new StringMenuItem("Graphics Scheme %s", 2, 2, /*'G'*/ 0, &_settingsChanged._videoType, imageMgr->getSetNames()));
|
|
_gfxMenu.add(MI_GFX_TILE_TRANSPARENCY, new BoolMenuItem("Transparency Hack %s", 2, 4, /*'t'*/ 0, &_settingsChanged._enhancementsOptions._u4TileTransparencyHack));
|
|
_gfxMenu.add(MI_GFX_TILE_TRANSPARENCY_SHADOW_SIZE, new IntMenuItem(" Shadow Size: %d", 2, 5, /*'s'*/ 9, &_settingsChanged._enhancementsOptions._u4TrileTransparencyHackShadowBreadth, 0, 16, 1));
|
|
_gfxMenu.add(MI_GFX_TILE_TRANSPARENCY_SHADOW_OPACITY, new IntMenuItem(" Shadow Opacity: %d", 2, 6, /*'o'*/ 9, &_settingsChanged._enhancementsOptions._u4TileTransparencyHackPixelShadowOpacity, 8, 256, 8));
|
|
_gfxMenu.add(MI_VIDEO_02, new StringMenuItem("Gem Layout %s", 2, 8,/*'e'*/ 1, &_settingsChanged._gemLayout, screenGetGemLayoutNames()));
|
|
_gfxMenu.add(MI_VIDEO_03, new StringMenuItem("Line Of Sight %s", 2, 9,/*'l'*/ 0, &_settingsChanged._lineOfSight, screenGetLineOfSightStyles()));
|
|
_gfxMenu.add(MI_VIDEO_07, new BoolMenuItem("Screen Shaking %s", 2, 10,/*'k'*/ 8, &_settingsChanged._screenShakes));
|
|
_gfxMenu.add(MI_GFX_RETURN, "\010 Return to Video Options", 2, 12,/*'r'*/ 2);
|
|
_gfxMenu.setClosesMenu(MI_GFX_RETURN);
|
|
|
|
|
|
_soundMenu.setTitle("Sound Options:", 0, 0);
|
|
_soundMenu.add(MI_SOUND_03, new BoolMenuItem("Fading %s", 2, 4,/*'f'*/ 0, &_settingsChanged._volumeFades));
|
|
_soundMenu.add(USE_SETTINGS, "\010 Use These Settings", 2, 11,/*'u'*/ 2);
|
|
_soundMenu.add(CANCEL, "\010 Cancel", 2, 12,/*'c'*/ 2);
|
|
_soundMenu.addShortcutKey(CANCEL, ' ');
|
|
_soundMenu.setClosesMenu(USE_SETTINGS);
|
|
_soundMenu.setClosesMenu(CANCEL);
|
|
|
|
_inputMenu.setTitle("Mouse Options:", 0, 0);
|
|
_inputMenu.add(MI_INPUT_03, new BoolMenuItem("Mouse %s", 2, 2,/*'m'*/ 0, &_settingsChanged._mouseOptions._enabled));
|
|
_inputMenu.add(USE_SETTINGS, "\010 Use These Settings", 2, 11,/*'u'*/ 2);
|
|
_inputMenu.add(CANCEL, "\010 Cancel", 2, 12,/*'c'*/ 2);
|
|
_inputMenu.addShortcutKey(CANCEL, ' ');
|
|
_inputMenu.setClosesMenu(USE_SETTINGS);
|
|
_inputMenu.setClosesMenu(CANCEL);
|
|
|
|
_speedMenu.setTitle("Speed Options:", 0, 0);
|
|
_speedMenu.add(MI_SPEED_01, new IntMenuItem("Game Cycles per Second %3d", 2, 2,/*'g'*/ 0, &_settingsChanged._gameCyclesPerSecond, 1, MAX_CYCLES_PER_SECOND, 1));
|
|
_speedMenu.add(MI_SPEED_02, new IntMenuItem("Battle Speed %3d", 2, 3,/*'b'*/ 0, &_settingsChanged._battleSpeed, 1, MAX_BATTLE_SPEED, 1));
|
|
_speedMenu.add(MI_SPEED_03, new IntMenuItem("Spell Effect Length %s", 2, 4,/*'p'*/ 1, &_settingsChanged._spellEffectSpeed, 1, MAX_SPELL_EFFECT_SPEED, 1, MENU_OUTPUT_SPELL));
|
|
_speedMenu.add(MI_SPEED_04, new IntMenuItem("Camping Length %3d sec", 2, 5,/*'m'*/ 2, &_settingsChanged._campTime, 1, MAX_CAMP_TIME, 1));
|
|
_speedMenu.add(MI_SPEED_05, new IntMenuItem("Inn Rest Length %3d sec", 2, 6,/*'i'*/ 0, &_settingsChanged._innTime, 1, MAX_INN_TIME, 1));
|
|
_speedMenu.add(MI_SPEED_06, new IntMenuItem("Shrine Meditation Length %3d sec", 2, 7,/*'s'*/ 0, &_settingsChanged._shrineTime, 1, MAX_SHRINE_TIME, 1));
|
|
_speedMenu.add(MI_SPEED_07, new IntMenuItem("Screen Shake Interval %3d msec", 2, 8,/*'r'*/ 2, &_settingsChanged._shakeInterval, MIN_SHAKE_INTERVAL, MAX_SHAKE_INTERVAL, 10));
|
|
_speedMenu.add(USE_SETTINGS, "\010 Use These Settings", 2, 11,/*'u'*/ 2);
|
|
_speedMenu.add(CANCEL, "\010 Cancel", 2, 12,/*'c'*/ 2);
|
|
_speedMenu.addShortcutKey(CANCEL, ' ');
|
|
_speedMenu.setClosesMenu(USE_SETTINGS);
|
|
_speedMenu.setClosesMenu(CANCEL);
|
|
|
|
/* move the BATTLE DIFFICULTY, DEBUG, and AUTOMATIC ACTIONS settings to "enhancementsOptions" */
|
|
_gameplayMenu.setTitle("Enhanced Gameplay Options:", 0, 0);
|
|
_gameplayMenu.add(MI_GAMEPLAY_01, new StringMenuItem("Battle Difficulty %s", 2, 2,/*'b'*/ 0, &_settingsChanged._battleDiff, settings.getBattleDiffs()));
|
|
_gameplayMenu.add(MI_GAMEPLAY_02, new BoolMenuItem("Fixed Chest Traps %s", 2, 3,/*'t'*/ 12, &_settingsChanged._enhancementsOptions._c64ChestTraps));
|
|
_gameplayMenu.add(MI_GAMEPLAY_03, new BoolMenuItem("Gazer Spawns Insects %s", 2, 4,/*'g'*/ 0, &_settingsChanged._enhancementsOptions._gazerSpawnsInsects));
|
|
_gameplayMenu.add(MI_GAMEPLAY_04, new BoolMenuItem("Gem View Shows Objects %s", 2, 5,/*'e'*/ 1, &_settingsChanged._enhancementsOptions._peerShowsObjects));
|
|
_gameplayMenu.add(MI_GAMEPLAY_05, new BoolMenuItem("Slime Divides %s", 2, 6,/*'s'*/ 0, &_settingsChanged._enhancementsOptions._slimeDivides));
|
|
_gameplayMenu.add(MI_GAMEPLAY_06, new BoolMenuItem("Debug Mode (Cheats) %s", 2, 8,/*'d'*/ 0, &_settingsChanged._debug));
|
|
_gameplayMenu.add(USE_SETTINGS, "\010 Use These Settings", 2, 11,/*'u'*/ 2);
|
|
_gameplayMenu.add(CANCEL, "\010 Cancel", 2, 12,/*'c'*/ 2);
|
|
_gameplayMenu.addShortcutKey(CANCEL, ' ');
|
|
_gameplayMenu.setClosesMenu(USE_SETTINGS);
|
|
_gameplayMenu.setClosesMenu(CANCEL);
|
|
|
|
_interfaceMenu.setTitle("Enhanced Interface Options:", 0, 0);
|
|
_interfaceMenu.add(MI_INTERFACE_01, new BoolMenuItem("Automatic Actions %s", 2, 2,/*'a'*/ 0, &_settingsChanged._shortcutCommands));
|
|
/* "(Open, Jimmy, etc.)" */
|
|
_interfaceMenu.add(MI_INTERFACE_02, new BoolMenuItem("Set Active Player %s", 2, 4,/*'p'*/ 11, &_settingsChanged._enhancementsOptions._activePlayer));
|
|
_interfaceMenu.add(MI_INTERFACE_03, new BoolMenuItem("Smart 'Enter' Key %s", 2, 5,/*'e'*/ 7, &_settingsChanged._enhancementsOptions._smartEnterKey));
|
|
_interfaceMenu.add(MI_INTERFACE_04, new BoolMenuItem("Text Colorization %s", 2, 6,/*'t'*/ 0, &_settingsChanged._enhancementsOptions._textColorization));
|
|
_interfaceMenu.add(MI_INTERFACE_05, new BoolMenuItem("Ultima V Shrines %s", 2, 7,/*'s'*/ 9, &_settingsChanged._enhancementsOptions._u5Shrines));
|
|
_interfaceMenu.add(MI_INTERFACE_06, new BoolMenuItem("Ultima V Spell Mixing %s", 2, 8,/*'m'*/ 15, &_settingsChanged._enhancementsOptions._u5SpellMixing));
|
|
_interfaceMenu.add(USE_SETTINGS, "\010 Use These Settings", 2, 11,/*'u'*/ 2);
|
|
_interfaceMenu.add(CANCEL, "\010 Cancel", 2, 12,/*'c'*/ 2);
|
|
_interfaceMenu.addShortcutKey(CANCEL, ' ');
|
|
_interfaceMenu.setClosesMenu(USE_SETTINGS);
|
|
_interfaceMenu.setClosesMenu(CANCEL);
|
|
}
|
|
|
|
IntroController::~IntroController() {
|
|
delete _mapScreen;
|
|
}
|
|
|
|
bool IntroController::init() {
|
|
_justInitiatedNewGame = false;
|
|
|
|
// sigData is referenced during Titles initialization
|
|
_binData = new IntroBinData();
|
|
_binData->load();
|
|
|
|
if (_bSkipTitles) {
|
|
// the init() method is called again from within the
|
|
// game via ALT-Q, so return to the menu
|
|
//
|
|
#ifndef IOS_ULTIMA4
|
|
_mode = INTRO_MENU;
|
|
#else
|
|
mode = INTRO_MAP;
|
|
#endif
|
|
_beastiesVisible = true;
|
|
_beastieOffset = 0;
|
|
g_music->intro();
|
|
} else {
|
|
// initialize the titles
|
|
initTitles();
|
|
_mode = INTRO_TITLES;
|
|
_beastiesVisible = false;
|
|
_beastieOffset = -32;
|
|
}
|
|
|
|
_beastie1Cycle = 0;
|
|
_beastie2Cycle = 0;
|
|
|
|
_sleepCycles = 0;
|
|
_scrPos = 0;
|
|
_objectStateTable = new IntroObjectState[INTRO_BASETILE_TABLE_SIZE];
|
|
|
|
_backgroundArea.reinit();
|
|
_menuArea.reinit();
|
|
_extendedMenuArea.reinit();
|
|
_questionArea.reinit();
|
|
_mapArea.reinit();
|
|
|
|
// only update the screen if we are returning from game mode
|
|
if (_bSkipTitles)
|
|
updateScreen();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IntroController::hasInitiatedNewGame() {
|
|
return this->_justInitiatedNewGame;
|
|
}
|
|
|
|
void IntroController::deleteIntro() {
|
|
delete _binData;
|
|
_binData = nullptr;
|
|
|
|
delete [] _objectStateTable;
|
|
_objectStateTable = nullptr;
|
|
|
|
imageMgr->freeIntroBackgrounds();
|
|
}
|
|
|
|
byte *IntroController::getSigData() {
|
|
assertMsg(_binData->_sigData != nullptr, "intro sig data not loaded");
|
|
return _binData->_sigData;
|
|
}
|
|
|
|
bool IntroController::keyPressed(int key) {
|
|
bool valid = true;
|
|
|
|
switch (_mode) {
|
|
case INTRO_TITLES:
|
|
// the user pressed a key to abort the sequence
|
|
skipTitles();
|
|
break;
|
|
|
|
case INTRO_MAP:
|
|
case INTRO_ABOUT:
|
|
_mode = INTRO_MENU;
|
|
updateScreen();
|
|
break;
|
|
|
|
case INTRO_MENU:
|
|
switch (key) {
|
|
case 'i':
|
|
_errorMessage.clear();
|
|
initiateNewGame();
|
|
break;
|
|
case 'j':
|
|
journeyOnward();
|
|
break;
|
|
case 'r':
|
|
_errorMessage.clear();
|
|
_mode = INTRO_MAP;
|
|
updateScreen();
|
|
break;
|
|
case 'c': {
|
|
_errorMessage.clear();
|
|
// Make a copy of our settings so we can change them
|
|
_settingsChanged = settings;
|
|
g_screen->screenDisableCursor();
|
|
runMenu(&_confMenu, &_extendedMenuArea, true);
|
|
g_screen->screenEnableCursor();
|
|
updateScreen();
|
|
break;
|
|
}
|
|
case 'a':
|
|
_errorMessage.clear();
|
|
about();
|
|
break;
|
|
case 'q':
|
|
EventHandler::end();
|
|
g_ultima->quitGame();
|
|
break;
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
g_music->introSwitch(key - '0');
|
|
break;
|
|
default:
|
|
valid = false;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
error("key handler called in wrong mode");
|
|
return true;
|
|
}
|
|
|
|
return valid || KeyHandler::defaultHandler(key, nullptr);
|
|
}
|
|
|
|
bool IntroController::mousePressed(const Common::Point &mousePos) {
|
|
switch (_mode) {
|
|
case INTRO_TITLES:
|
|
// Finish the title sequence
|
|
skipTitles();
|
|
break;
|
|
|
|
case INTRO_MAP:
|
|
case INTRO_ABOUT:
|
|
_mode = INTRO_MENU;
|
|
updateScreen();
|
|
break;
|
|
|
|
case INTRO_MENU: {
|
|
char key = _menuArea.getOptionAt(mousePos);
|
|
if (key)
|
|
keyPressed(key);
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void IntroController::drawMap() {
|
|
if (0 && _sleepCycles > 0) {
|
|
drawMapStatic();
|
|
drawMapAnimated();
|
|
_sleepCycles--;
|
|
} else {
|
|
byte commandNibble;
|
|
byte dataNibble;
|
|
|
|
do {
|
|
commandNibble = _binData->_scriptTable[_scrPos] >> 4;
|
|
|
|
switch (commandNibble) {
|
|
/* 0-4 = set object position and tile frame */
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
/* ----------------------------------------------------------
|
|
Set object position and tile frame
|
|
Format: yi [t(3); x(5)]
|
|
i = table index
|
|
x = x coordinate (5 least significant bits of second byte)
|
|
y = y coordinate
|
|
t = tile frame (3 most significant bits of second byte)
|
|
---------------------------------------------------------- */
|
|
dataNibble = _binData->_scriptTable[_scrPos] & 0xf;
|
|
_objectStateTable[dataNibble].x = _binData->_scriptTable[_scrPos + 1] & 0x1f;
|
|
_objectStateTable[dataNibble].y = commandNibble;
|
|
|
|
// See if the tile id needs to be recalculated
|
|
if ((_binData->_scriptTable[_scrPos + 1] >> 5) >= _binData->_baseTileTable[dataNibble]->getFrames()) {
|
|
int frame = (_binData->_scriptTable[_scrPos + 1] >> 5) - _binData->_baseTileTable[dataNibble]->getFrames();
|
|
_objectStateTable[dataNibble].tile = MapTile(_binData->_baseTileTable[dataNibble]->getId() + 1);
|
|
_objectStateTable[dataNibble].tile._frame = frame;
|
|
} else {
|
|
_objectStateTable[dataNibble].tile = MapTile(_binData->_baseTileTable[dataNibble]->getId());
|
|
_objectStateTable[dataNibble].tile._frame = (_binData->_scriptTable[_scrPos + 1] >> 5);
|
|
}
|
|
|
|
_scrPos += 2;
|
|
break;
|
|
case 7:
|
|
/* ---------------
|
|
Delete object
|
|
Format: 7i
|
|
i = table index
|
|
--------------- */
|
|
dataNibble = _binData->_scriptTable[_scrPos] & 0xf;
|
|
_objectStateTable[dataNibble].tile = 0;
|
|
_scrPos++;
|
|
break;
|
|
case 8:
|
|
/* ----------------------------------------------
|
|
Redraw intro map and objects, then go to sleep
|
|
Format: 8c
|
|
c = cycles to sleep
|
|
---------------------------------------------- */
|
|
drawMapStatic();
|
|
drawMapAnimated();
|
|
|
|
_mapScreen->drawSubRect(
|
|
SCALED(8),
|
|
SCALED(13 * 8),
|
|
SCALED(8),
|
|
SCALED(13 * 8),
|
|
SCALED(38 * 8),
|
|
SCALED(10 * 8));
|
|
|
|
/* set sleep cycles */
|
|
_sleepCycles = _binData->_scriptTable[_scrPos] & 0xf;
|
|
_scrPos++;
|
|
break;
|
|
case 0xf:
|
|
/* -------------------------------------
|
|
Jump to the start of the script table
|
|
Format: f?
|
|
? = doesn't matter
|
|
------------------------------------- */
|
|
_scrPos = 0;
|
|
break;
|
|
default:
|
|
/* invalid command */
|
|
_scrPos++;
|
|
break;
|
|
}
|
|
|
|
} while (commandNibble != 8);
|
|
}
|
|
}
|
|
|
|
void IntroController::drawMapStatic() {
|
|
int x, y;
|
|
|
|
// draw unmodified map
|
|
for (y = 0; y < INTRO_MAP_HEIGHT; y++)
|
|
for (x = 0; x < INTRO_MAP_WIDTH; x++)
|
|
_mapArea.drawTile(_binData->_introMap[x + (y * INTRO_MAP_WIDTH)], false, x, y);
|
|
}
|
|
|
|
void IntroController::drawMapAnimated() {
|
|
int i;
|
|
|
|
// draw animated objects
|
|
for (i = 0; i < INTRO_BASETILE_TABLE_SIZE; i++)
|
|
if (_objectStateTable[i].tile != 0) {
|
|
Std::vector<MapTile> tiles;
|
|
tiles.push_back(_objectStateTable[i].tile);
|
|
tiles.push_back(_binData->_introMap[_objectStateTable[i].x + (_objectStateTable[i].y * INTRO_MAP_WIDTH)]);
|
|
_mapArea.drawTile(tiles, false, _objectStateTable[i].x, _objectStateTable[i].y);
|
|
}
|
|
}
|
|
|
|
void IntroController::drawBeasties() {
|
|
drawBeastie(0, _beastieOffset, _binData->_beastie1FrameTable[_beastie1Cycle]);
|
|
drawBeastie(1, _beastieOffset, _binData->_beastie2FrameTable[_beastie2Cycle]);
|
|
if (_beastieOffset < 0)
|
|
_beastieOffset++;
|
|
}
|
|
|
|
void IntroController::drawBeastie(int beast, int vertoffset, int frame) {
|
|
char buffer[128];
|
|
int destx;
|
|
|
|
assertMsg(beast == 0 || beast == 1, "invalid beast: %d", beast);
|
|
|
|
Common::sprintf_s(buffer, "beast%dframe%02d", beast, frame);
|
|
|
|
destx = beast ? (320 - 48) : 0;
|
|
_backgroundArea.draw(buffer, destx, vertoffset);
|
|
}
|
|
|
|
void IntroController::animateTree(const Common::String &frame) {
|
|
_backgroundArea.draw(frame, 72, 68);
|
|
}
|
|
|
|
void IntroController::drawCard(int pos, int card) {
|
|
static const char *cardNames[] = {
|
|
"honestycard", "compassioncard", "valorcard", "justicecard",
|
|
"sacrificecard", "honorcard", "spiritualitycard", "humilitycard"
|
|
};
|
|
|
|
assertMsg(pos == 0 || pos == 1, "invalid pos: %d", pos);
|
|
assertMsg(card >= 0 && card < 8, "invalid card: %d", card);
|
|
|
|
_backgroundArea.draw(cardNames[card], pos ? 218 : 12, 12);
|
|
}
|
|
|
|
void IntroController::drawAbacusBeads(int row, int selectedVirtue, int rejectedVirtue) {
|
|
assertMsg(row >= 0 && row < 7, "invalid row: %d", row);
|
|
assertMsg(selectedVirtue < 8 && selectedVirtue >= 0, "invalid virtue: %d", selectedVirtue);
|
|
assertMsg(rejectedVirtue < 8 && rejectedVirtue >= 0, "invalid virtue: %d", rejectedVirtue);
|
|
|
|
_backgroundArea.draw("whitebead", 128 + (selectedVirtue * 9), 24 + (row * 15));
|
|
_backgroundArea.draw("blackbead", 128 + (rejectedVirtue * 9), 24 + (row * 15));
|
|
}
|
|
|
|
void IntroController::updateScreen() {
|
|
g_screen->screenHideCursor();
|
|
_menuArea.clearOptions();
|
|
|
|
switch (_mode) {
|
|
case INTRO_MAP:
|
|
_backgroundArea.draw(BKGD_INTRO);
|
|
drawMap();
|
|
drawBeasties();
|
|
// display the profile name if a local profile is being used
|
|
if (_useProfile)
|
|
g_screen->screenTextAt(40 - _profileName.size(), 24, "%s", _profileName.c_str());
|
|
break;
|
|
|
|
case INTRO_MENU:
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_INTRO);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
|
|
// if there is an error message to display, show it
|
|
if (!_errorMessage.empty()) {
|
|
_menuArea.textAt(6, 5, "%s", _errorMessage.c_str());
|
|
drawBeasties();
|
|
g_screen->update();
|
|
// wait for a couple seconds
|
|
EventHandler::wait_msecs(2000);
|
|
// clear the screen again
|
|
_errorMessage.clear();
|
|
_backgroundArea.draw(BKGD_INTRO);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
_menuArea.textAt(1, 1, "In another world, in a time to come.");
|
|
_menuArea.textAt(14, 3, "Options:");
|
|
_menuArea.optionAt(10, 5, 'r', "%s", _menuArea.colorizeString("Return to the view", FG_YELLOW, 0, 1).c_str());
|
|
_menuArea.optionAt(10, 6, 'j', "%s", _menuArea.colorizeString("Journey Onward", FG_YELLOW, 0, 1).c_str());
|
|
_menuArea.optionAt(10, 7, 'i', "%s", _menuArea.colorizeString("Initiate New Game", FG_YELLOW, 0, 1).c_str());
|
|
_menuArea.optionAt(10, 8, 'c', "%s", _menuArea.colorizeString("Configure", FG_YELLOW, 0, 1).c_str());
|
|
_menuArea.optionAt(10, 9, 'a', "%s", _menuArea.colorizeString("About", FG_YELLOW, 0, 1).c_str());
|
|
drawBeasties();
|
|
|
|
// draw the cursor last
|
|
g_screen->screenSetCursorPos(24, 16);
|
|
g_screen->screenShowCursor();
|
|
break;
|
|
|
|
default:
|
|
error("bad mode in updateScreen");
|
|
}
|
|
|
|
g_screen->screenUpdateCursor();
|
|
g_screen->update();
|
|
}
|
|
|
|
void IntroController::initiateNewGame() {
|
|
// disable the screen cursor because a text cursor will now be used
|
|
g_screen->screenDisableCursor();
|
|
_menuArea.clear();
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_INTRO);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
|
|
// display name prompt and read name from keyboard
|
|
_menuArea.textAt(3, 3, "By what name shalt thou be known");
|
|
_menuArea.textAt(3, 4, "in this world and time?");
|
|
|
|
// enable the text cursor after setting it's initial position
|
|
// this will avoid drawing in undesirable areas like 0,0
|
|
_menuArea.setCursorPos(11, 7, false);
|
|
_menuArea.setCursorFollowsText(true);
|
|
_menuArea.enableCursor();
|
|
|
|
drawBeasties();
|
|
g_screen->update();
|
|
|
|
Common::String nameBuffer = ReadStringController::get(12, &_menuArea);
|
|
if (nameBuffer.empty() || shouldQuit()) {
|
|
// the user didn't enter a name
|
|
_menuArea.disableCursor();
|
|
g_screen->screenEnableCursor();
|
|
updateScreen();
|
|
return;
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_INTRO);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
|
|
// display sex prompt and read sex from keyboard
|
|
_menuArea.textAt(3, 3, "Art thou Male or Female?");
|
|
|
|
// the cursor is already enabled, just change its position
|
|
_menuArea.setCursorPos(28, 3, true);
|
|
|
|
drawBeasties();
|
|
|
|
SexType sex;
|
|
int sexChoice = ReadChoiceController::get("mf");
|
|
|
|
if (!shouldQuit()) {
|
|
if (sexChoice == 'm')
|
|
sex = SEX_MALE;
|
|
else
|
|
sex = SEX_FEMALE;
|
|
|
|
finishInitiateGame(nameBuffer, sex);
|
|
}
|
|
}
|
|
|
|
void IntroController::finishInitiateGame(const Common::String &nameBuffer, SexType sex) {
|
|
#ifdef IOS_ULTIMA4
|
|
mode = INTRO_MENU; // ensure we are now in the menu mode, (i.e., stop drawing the map).
|
|
#endif
|
|
// no more text entry, so disable the text cursor
|
|
_menuArea.disableCursor();
|
|
|
|
// show the lead up story
|
|
if (!shouldQuit())
|
|
showStory();
|
|
|
|
// ask questions that determine character class
|
|
if (!shouldQuit())
|
|
startQuestions();
|
|
|
|
if (!shouldQuit()) {
|
|
// Setup savegame fields. The original wrote out multiple files and
|
|
// then loaded them up once the game starts. Now we're simply setting
|
|
// up the savegame fields and letting the game read from them later,
|
|
// as if a savegame had been loaded
|
|
SaveGame &saveGame = *g_ultima->_saveGame;
|
|
SaveGamePlayerRecord avatar;
|
|
avatar.init();
|
|
strncpy(avatar._name, nameBuffer.c_str(), 15);
|
|
avatar._name[15] = '\0';
|
|
|
|
avatar._sex = sex;
|
|
saveGame.init(&avatar);
|
|
g_screen->screenHideCursor();
|
|
initPlayers(&saveGame);
|
|
saveGame._food = 30000;
|
|
saveGame._gold = 200;
|
|
saveGame._reagents[REAG_GINSENG] = 3;
|
|
saveGame._reagents[REAG_GARLIC] = 4;
|
|
saveGame._torches = 2;
|
|
|
|
_justInitiatedNewGame = true;
|
|
|
|
// show the text that's segues into the main game
|
|
showText(_binData->_introGypsy[GYP_SEGUE1]);
|
|
#ifdef IOS_ULTIMA4
|
|
U4IOS::switchU4IntroControllerToContinueButton();
|
|
#endif
|
|
ReadChoiceController pauseController("");
|
|
eventHandler->pushController(&pauseController);
|
|
pauseController.waitFor();
|
|
|
|
showText(_binData->_introGypsy[GYP_SEGUE2]);
|
|
|
|
eventHandler->pushController(&pauseController);
|
|
pauseController.waitFor();
|
|
}
|
|
|
|
// done: exit intro and let game begin
|
|
_questionArea.disableCursor();
|
|
EventHandler::setControllerDone();
|
|
}
|
|
|
|
void IntroController::showStory() {
|
|
ReadChoiceController pauseController("");
|
|
|
|
_beastiesVisible = false;
|
|
|
|
_questionArea.setCursorFollowsText(true);
|
|
|
|
for (int storyInd = 0; storyInd < 24 && !g_ultima->shouldQuit(); storyInd++) {
|
|
if (storyInd == 0)
|
|
_backgroundArea.draw(BKGD_TREE);
|
|
else if (storyInd == 3)
|
|
animateTree("moongate");
|
|
else if (storyInd == 5)
|
|
animateTree("items");
|
|
else if (storyInd == 6)
|
|
_backgroundArea.draw(BKGD_PORTAL);
|
|
else if (storyInd == 11)
|
|
_backgroundArea.draw(BKGD_TREE);
|
|
else if (storyInd == 15)
|
|
_backgroundArea.draw(BKGD_OUTSIDE);
|
|
else if (storyInd == 17)
|
|
_backgroundArea.draw(BKGD_INSIDE);
|
|
else if (storyInd == 20)
|
|
_backgroundArea.draw(BKGD_WAGON);
|
|
else if (storyInd == 21)
|
|
_backgroundArea.draw(BKGD_GYPSY);
|
|
else if (storyInd == 23)
|
|
_backgroundArea.draw(BKGD_ABACUS);
|
|
|
|
showText(_binData->_introText[storyInd]);
|
|
|
|
eventHandler->pushController(&pauseController);
|
|
// enable the cursor here to avoid drawing in undesirable locations
|
|
_questionArea.enableCursor();
|
|
pauseController.waitFor();
|
|
}
|
|
}
|
|
|
|
void IntroController::startQuestions() {
|
|
ReadChoiceController pauseController("");
|
|
ReadChoiceController questionController("ab");
|
|
|
|
_questionRound = 0;
|
|
initQuestionTree();
|
|
|
|
while (!shouldQuit()) {
|
|
// draw the abacus background, if necessary
|
|
if (_questionRound == 0)
|
|
_backgroundArea.draw(BKGD_ABACUS);
|
|
|
|
// draw the cards and show the lead up text
|
|
drawCard(0, _questionTree[_questionRound * 2]);
|
|
drawCard(1, _questionTree[_questionRound * 2 + 1]);
|
|
|
|
_questionArea.clear();
|
|
_questionArea.textAt(0, 0, "%s", _binData->_introGypsy[_questionRound == 0 ? GYP_PLACES_FIRST : (_questionRound == 6 ? GYP_PLACES_LAST : GYP_PLACES_TWOMORE)].c_str());
|
|
_questionArea.textAt(0, 1, "%s", _binData->_introGypsy[GYP_UPON_TABLE].c_str());
|
|
_questionArea.textAt(0, 2, "%s and %s. She says",
|
|
_binData->_introGypsy[_questionTree[_questionRound * 2] + 4].c_str(),
|
|
_binData->_introGypsy[_questionTree[_questionRound * 2 + 1] + 4].c_str());
|
|
_questionArea.textAt(0, 3, "\"Consider this:\"");
|
|
|
|
#ifdef IOS_ULTIMA4
|
|
U4IOS::switchU4IntroControllerToContinueButton();
|
|
#endif
|
|
// wait for a key
|
|
eventHandler->pushController(&pauseController);
|
|
pauseController.waitFor();
|
|
|
|
g_screen->screenEnableCursor();
|
|
// show the question to choose between virtues
|
|
showText(getQuestion(_questionTree[_questionRound * 2], _questionTree[_questionRound * 2 + 1]));
|
|
|
|
#ifdef IOS_ULTIMA4
|
|
U4IOS::switchU4IntroControllerToABButtons();
|
|
#endif
|
|
// wait for an answer
|
|
int choice;
|
|
do {
|
|
eventHandler->pushController(&questionController);
|
|
choice = questionController.waitFor();
|
|
} while (!shouldQuit() && choice == -1);
|
|
|
|
// update the question tree
|
|
if (shouldQuit() || doQuestion(choice == 'a' ? 0 : 1)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Common::String IntroController::getQuestion(int v1, int v2) {
|
|
int i = 0;
|
|
int d = 7;
|
|
|
|
assertMsg(v1 < v2, "first virtue must be smaller (v1 = %d, v2 = %d)", v1, v2);
|
|
|
|
while (v1 > 0) {
|
|
i += d;
|
|
d--;
|
|
v1--;
|
|
v2--;
|
|
}
|
|
|
|
assertMsg((i + v2 - 1) < 28, "calculation failed");
|
|
|
|
return _binData->_introQuestions[i + v2 - 1];
|
|
}
|
|
|
|
void IntroController::journeyOnward() {
|
|
bool validSave = false;
|
|
int lastSave = ConfMan.hasKey("last_save") ? ConfMan.getInt("last_save") : -1;
|
|
|
|
if (lastSave != -1) {
|
|
// At this point the game context hasn't been created yet, so we only
|
|
// want to validate that the given savegame file exists without loading it
|
|
Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(
|
|
g_ultima->getSaveStateName(lastSave));
|
|
validSave = saveFile != nullptr;
|
|
delete saveFile;
|
|
}
|
|
|
|
if (validSave) {
|
|
EventHandler::setControllerDone();
|
|
g_ultima->setToJourneyOnwards();
|
|
} else {
|
|
_errorMessage = "Initiate a new game first!";
|
|
updateScreen();
|
|
g_screen->update();
|
|
}
|
|
}
|
|
|
|
void IntroController::about() {
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_INTRO);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
|
|
g_screen->screenHideCursor();
|
|
_menuArea.textAt(11, 1, "ScummVM Ultima IV");
|
|
_menuArea.textAt(1, 3, "Based on the xu4 project");
|
|
drawBeasties();
|
|
|
|
_mode = INTRO_ABOUT;
|
|
}
|
|
|
|
void IntroController::showText(const Common::String &text) {
|
|
Common::String current = text;
|
|
int lineNo = 0;
|
|
|
|
_questionArea.clear();
|
|
|
|
unsigned long pos = current.find("\n");
|
|
while (pos < current.size()) {
|
|
_questionArea.textAt(0, lineNo++, "%s", current.substr(0, pos).c_str());
|
|
current = current.substr(pos + 1);
|
|
pos = current.find("\n");
|
|
}
|
|
|
|
/* write the last line (possibly only line) */
|
|
_questionArea.textAt(0, lineNo++, "%s", current.substr(0, pos).c_str());
|
|
}
|
|
|
|
void IntroController::runMenu(Menu *menu, TextView *view, bool withBeasties) {
|
|
menu->addObserver(this);
|
|
menu->reset();
|
|
|
|
// if the menu has an extended height, fill the menu background, otherwise reset the display
|
|
menu->show(view);
|
|
if (withBeasties)
|
|
drawBeasties();
|
|
|
|
MenuController menuController(menu, view);
|
|
eventHandler->pushController(&menuController);
|
|
menuController.waitFor();
|
|
|
|
// enable the cursor here, after the menu has been established
|
|
view->enableCursor();
|
|
menu->deleteObserver(this);
|
|
view->disableCursor();
|
|
}
|
|
|
|
void IntroController::timerFired() {
|
|
g_screen->screenCycle();
|
|
g_screen->screenUpdateCursor();
|
|
|
|
if (_mode == INTRO_TITLES)
|
|
if (updateTitle() == false) {
|
|
// setup the map screen
|
|
_mode = INTRO_MAP;
|
|
_beastiesVisible = true;
|
|
g_music->intro();
|
|
updateScreen();
|
|
}
|
|
|
|
if (_mode == INTRO_MAP)
|
|
drawMap();
|
|
|
|
if (_beastiesVisible)
|
|
drawBeasties();
|
|
|
|
if (xu4_random(2) && ++_beastie1Cycle >= BEASTIE1_FRAMES)
|
|
_beastie1Cycle = 0;
|
|
if (xu4_random(2) && ++_beastie2Cycle >= BEASTIE2_FRAMES)
|
|
_beastie2Cycle = 0;
|
|
}
|
|
|
|
void IntroController::update(Menu *menu, MenuEvent &event) {
|
|
if (menu == &_confMenu)
|
|
updateConfMenu(event);
|
|
else if (menu == &_videoMenu)
|
|
updateVideoMenu(event);
|
|
else if (menu == &_gfxMenu)
|
|
updateGfxMenu(event);
|
|
else if (menu == &_soundMenu)
|
|
updateSoundMenu(event);
|
|
else if (menu == &_inputMenu)
|
|
updateInputMenu(event);
|
|
else if (menu == &_speedMenu)
|
|
updateSpeedMenu(event);
|
|
else if (menu == &_gameplayMenu)
|
|
updateGameplayMenu(event);
|
|
else if (menu == &_interfaceMenu)
|
|
updateInterfaceMenu(event);
|
|
|
|
// beasties are always visible on the menus
|
|
drawBeasties();
|
|
}
|
|
|
|
void IntroController::updateConfMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
// show or hide game enhancement options if enhancements are enabled/disabled
|
|
_confMenu.getItemById(MI_CONF_GAMEPLAY)->setVisible(_settingsChanged._enhancements);
|
|
_confMenu.getItemById(MI_CONF_INTERFACE)->setVisible(_settingsChanged._enhancements);
|
|
|
|
// save settings
|
|
settings.setData(_settingsChanged);
|
|
settings.write();
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case MI_CONF_VIDEO:
|
|
runMenu(&_videoMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case MI_VIDEO_CONF_GFX:
|
|
runMenu(&_gfxMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case MI_CONF_SOUND:
|
|
runMenu(&_soundMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case MI_CONF_INPUT:
|
|
runMenu(&_inputMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case MI_CONF_SPEED:
|
|
runMenu(&_speedMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case MI_CONF_GAMEPLAY:
|
|
runMenu(&_gameplayMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case MI_CONF_INTERFACE:
|
|
runMenu(&_interfaceMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case CANCEL:
|
|
// discard settings
|
|
_settingsChanged = settings;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
void IntroController::updateVideoMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case USE_SETTINGS:
|
|
// Save settings (if necessary)
|
|
if (settings != _settingsChanged) {
|
|
settings.setData(_settingsChanged);
|
|
settings.write();
|
|
|
|
// FIXME: resize images, etc.
|
|
g_screen->screenReInit();
|
|
|
|
// Go back to menu mode
|
|
_mode = INTRO_MENU;
|
|
}
|
|
break;
|
|
case MI_VIDEO_CONF_GFX:
|
|
runMenu(&_gfxMenu, &_extendedMenuArea, true);
|
|
break;
|
|
case CANCEL:
|
|
// discard settings
|
|
_settingsChanged = settings;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
void IntroController::updateGfxMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case MI_GFX_RETURN:
|
|
runMenu(&_videoMenu, &_extendedMenuArea, true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
void IntroController::updateSoundMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case USE_SETTINGS:
|
|
// save settings
|
|
settings.setData(_settingsChanged);
|
|
settings.write();
|
|
g_music->intro();
|
|
break;
|
|
case CANCEL:
|
|
// discard settings
|
|
_settingsChanged = settings;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
void IntroController::updateInputMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case USE_SETTINGS:
|
|
// save settings
|
|
settings.setData(_settingsChanged);
|
|
settings.write();
|
|
break;
|
|
case CANCEL:
|
|
// discard settings
|
|
_settingsChanged = settings;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
void IntroController::updateSpeedMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case USE_SETTINGS:
|
|
// save settings
|
|
settings.setData(_settingsChanged);
|
|
settings.write();
|
|
|
|
// re-initialize events
|
|
settings._eventTimerGranularity = (1000 / settings._gameCyclesPerSecond);
|
|
eventHandler->getTimer()->reset(settings._eventTimerGranularity);
|
|
|
|
break;
|
|
case CANCEL:
|
|
// discard settings
|
|
_settingsChanged = settings;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
void IntroController::updateGameplayMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case USE_SETTINGS:
|
|
// save settings
|
|
settings.setData(_settingsChanged);
|
|
settings.write();
|
|
break;
|
|
case CANCEL:
|
|
// discard settings
|
|
_settingsChanged = settings;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
}
|
|
|
|
void IntroController::updateInterfaceMenu(MenuEvent &event) {
|
|
if (event.getType() == MenuEvent::ACTIVATE ||
|
|
event.getType() == MenuEvent::INCREMENT ||
|
|
event.getType() == MenuEvent::DECREMENT) {
|
|
|
|
switch (event.getMenuItem()->getId()) {
|
|
case USE_SETTINGS:
|
|
// save settings
|
|
settings.setData(_settingsChanged);
|
|
settings.write();
|
|
break;
|
|
case CANCEL:
|
|
// discard settings
|
|
_settingsChanged = settings;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the extended background for all option screens
|
|
_backgroundArea.draw(BKGD_OPTIONS_TOP, 0, 0);
|
|
_backgroundArea.draw(BKGD_OPTIONS_BTM, 0, 120);
|
|
|
|
// after drawing the menu, extra menu text can be added here
|
|
_extendedMenuArea.textAt(2, 3, " (Open, Jimmy, etc.)");
|
|
}
|
|
|
|
void IntroController::initQuestionTree() {
|
|
int i, r;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
_questionTree[i] = i;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
r = xu4_random(8);
|
|
SWAP(_questionTree[r], _questionTree[i]);
|
|
}
|
|
_answerInd = 8;
|
|
|
|
if (_questionTree[0] > _questionTree[1]) {
|
|
SWAP(_questionTree[0], _questionTree[1]);
|
|
}
|
|
}
|
|
|
|
bool IntroController::doQuestion(int answer) {
|
|
if (!answer)
|
|
_questionTree[_answerInd] = _questionTree[_questionRound * 2];
|
|
else
|
|
_questionTree[_answerInd] = _questionTree[_questionRound * 2 + 1];
|
|
|
|
drawAbacusBeads(_questionRound, _questionTree[_answerInd],
|
|
_questionTree[_questionRound * 2 + ((answer) ? 0 : 1)]);
|
|
|
|
_answerInd++;
|
|
_questionRound++;
|
|
|
|
if (_questionRound > 6)
|
|
return true;
|
|
|
|
if (_questionTree[_questionRound * 2] > _questionTree[_questionRound * 2 + 1]) {
|
|
SWAP(_questionTree[_questionRound * 2],
|
|
_questionTree[_questionRound * 2 + 1]);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void IntroController::initPlayers(SaveGame *saveGame) {
|
|
int i, p;
|
|
static const struct {
|
|
WeaponType weapon;
|
|
ArmorType armor;
|
|
int level, xp, x, y;
|
|
} initValuesForClass[] = {
|
|
{ WEAP_STAFF, ARMR_CLOTH, 2, 125, 231, 136 }, /* CLASS_MAGE */
|
|
{ WEAP_SLING, ARMR_CLOTH, 3, 240, 83, 105 }, /* CLASS_BARD */
|
|
{ WEAP_AXE, ARMR_LEATHER, 3, 205, 35, 221 }, /* CLASS_FIGHTER */
|
|
{ WEAP_DAGGER, ARMR_CLOTH, 2, 175, 59, 44 }, /* CLASS_DRUID */
|
|
{ WEAP_MACE, ARMR_LEATHER, 2, 110, 158, 21 }, /* CLASS_TINKER */
|
|
{ WEAP_SWORD, ARMR_CHAIN, 3, 325, 105, 183 }, /* CLASS_PALADIN */
|
|
{ WEAP_SWORD, ARMR_LEATHER, 2, 150, 23, 129 }, /* CLASS_RANGER */
|
|
{ WEAP_STAFF, ARMR_CLOTH, 1, 5, 186, 171 } /* CLASS_SHEPHERD */
|
|
};
|
|
static const struct {
|
|
const char *name;
|
|
int str, dex, intel;
|
|
SexType sex;
|
|
} initValuesForNpcClass[] = {
|
|
{ "Mariah", 9, 12, 20, SEX_FEMALE }, /* CLASS_MAGE */
|
|
{ "Iolo", 16, 19, 13, SEX_MALE }, /* CLASS_BARD */
|
|
{ "Geoffrey", 20, 15, 11, SEX_MALE }, /* CLASS_FIGHTER */
|
|
{ "Jaana", 17, 16, 13, SEX_FEMALE }, /* CLASS_DRUID */
|
|
{ "Julia", 15, 16, 12, SEX_FEMALE }, /* CLASS_TINKER */
|
|
{ "Dupre", 17, 14, 17, SEX_MALE }, /* CLASS_PALADIN */
|
|
{ "Shamino", 16, 15, 15, SEX_MALE }, /* CLASS_RANGER */
|
|
{ "Katrina", 11, 12, 10, SEX_FEMALE } /* CLASS_SHEPHERD */
|
|
};
|
|
|
|
saveGame->_players[0]._class = static_cast<ClassType>(_questionTree[14]);
|
|
|
|
assertMsg((int)saveGame->_players[0]._class < 8, "bad class: %d", saveGame->_players[0]._class);
|
|
saveGame->_positions.resize(1);
|
|
saveGame->_positions[0] = LocationCoords(MAP_WORLD,
|
|
initValuesForClass[saveGame->_players[0]._class].x,
|
|
initValuesForClass[saveGame->_players[0]._class].y,
|
|
0);
|
|
|
|
saveGame->_players[0]._weapon = initValuesForClass[saveGame->_players[0]._class].weapon;
|
|
saveGame->_players[0]._armor = initValuesForClass[saveGame->_players[0]._class].armor;
|
|
saveGame->_players[0]._xp = initValuesForClass[saveGame->_players[0]._class].xp;
|
|
saveGame->_players[0]._str = 15;
|
|
saveGame->_players[0]._dex = 15;
|
|
saveGame->_players[0]._intel = 15;
|
|
|
|
for (i = 0; i < VIRT_MAX; i++)
|
|
saveGame->_karma[i] = 50;
|
|
|
|
for (i = 8; i < 15; i++) {
|
|
saveGame->_karma[_questionTree[i]] += 5;
|
|
switch (_questionTree[i]) {
|
|
case VIRT_HONESTY:
|
|
saveGame->_players[0]._intel += 3;
|
|
break;
|
|
case VIRT_COMPASSION:
|
|
saveGame->_players[0]._dex += 3;
|
|
break;
|
|
case VIRT_VALOR:
|
|
saveGame->_players[0]._str += 3;
|
|
break;
|
|
case VIRT_JUSTICE:
|
|
saveGame->_players[0]._intel++;
|
|
saveGame->_players[0]._dex++;
|
|
break;
|
|
case VIRT_SACRIFICE:
|
|
saveGame->_players[0]._dex++;
|
|
saveGame->_players[0]._str++;
|
|
break;
|
|
case VIRT_HONOR:
|
|
saveGame->_players[0]._intel++;
|
|
saveGame->_players[0]._str++;
|
|
break;
|
|
case VIRT_SPIRITUALITY:
|
|
saveGame->_players[0]._intel++;
|
|
saveGame->_players[0]._dex++;
|
|
saveGame->_players[0]._str++;
|
|
break;
|
|
case VIRT_HUMILITY:
|
|
/* no stats for you! */
|
|
break;
|
|
}
|
|
}
|
|
|
|
PartyMember player(nullptr, &saveGame->_players[0]);
|
|
saveGame->_players[0]._hp = saveGame->_players[0]._hpMax = player.getMaxLevel() * 100;
|
|
saveGame->_players[0]._mp = player.getMaxMp();
|
|
|
|
p = 1;
|
|
for (i = 0; i < VIRT_MAX; i++) {
|
|
player = PartyMember(nullptr, &saveGame->_players[i]);
|
|
|
|
/* Initial setup for party members that aren't in your group yet... */
|
|
if (i != saveGame->_players[0]._class) {
|
|
saveGame->_players[p]._class = static_cast<ClassType>(i);
|
|
saveGame->_players[p]._xp = initValuesForClass[i].xp;
|
|
saveGame->_players[p]._str = initValuesForNpcClass[i].str;
|
|
saveGame->_players[p]._dex = initValuesForNpcClass[i].dex;
|
|
saveGame->_players[p]._intel = initValuesForNpcClass[i].intel;
|
|
saveGame->_players[p]._weapon = initValuesForClass[i].weapon;
|
|
saveGame->_players[p]._armor = initValuesForClass[i].armor;
|
|
strncpy(saveGame->_players[p]._name, initValuesForNpcClass[i].name, 15);
|
|
saveGame->_players[p]._name[15] = '\0';
|
|
saveGame->_players[p]._sex = initValuesForNpcClass[i].sex;
|
|
saveGame->_players[p]._hp = saveGame->_players[p]._hpMax = initValuesForClass[i].level * 100;
|
|
saveGame->_players[p]._mp = player.getMaxMp();
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void IntroController::preloadMap() {
|
|
int x, y, i;
|
|
|
|
// draw unmodified map
|
|
for (y = 0; y < INTRO_MAP_HEIGHT; y++)
|
|
for (x = 0; x < INTRO_MAP_WIDTH; x++)
|
|
_mapArea.loadTile(_binData->_introMap[x + (y * INTRO_MAP_WIDTH)]);
|
|
|
|
// draw animated objects
|
|
for (i = 0; i < INTRO_BASETILE_TABLE_SIZE; i++) {
|
|
if (_objectStateTable[i].tile != 0)
|
|
_mapArea.loadTile(_objectStateTable[i].tile);
|
|
}
|
|
}
|
|
|
|
|
|
void IntroController::initTitles() {
|
|
// add the intro elements
|
|
// x, y, w, h, method, delay, duration
|
|
//
|
|
addTitle(97, 0, 130, 16, SIGNATURE, 1000, 3000); // "Lord British"
|
|
addTitle(148, 17, 24, 4, AND, 1000, 100); // "and"
|
|
addTitle(84, 31, 152, 1, BAR, 1000, 500); // <bar>
|
|
addTitle(86, 21, 148, 9, ORIGIN, 1000, 100); // "Origin Systems, Inc."
|
|
addTitle(133, 33, 54, 5, PRESENT, 0, 100); // "present"
|
|
addTitle(59, 33, 202, 46, TITLE, 1000, 5000); // "Ultima IV"
|
|
addTitle(40, 80, 240, 13, SUBTITLE, 1000, 100); // "Quest of the Avatar"
|
|
addTitle(0, 96, 320, 96, MAP, 1000, 100); // the map
|
|
|
|
// get the source data for the elements
|
|
getTitleSourceData();
|
|
|
|
// reset the iterator
|
|
_title = _titles.begin();
|
|
|
|
// speed up the timer while the intro titles are displayed
|
|
eventHandler->getTimer()->reset(settings._titleSpeedOther);
|
|
}
|
|
|
|
void IntroController::addTitle(int x, int y, int w, int h, AnimType method, uint32 delay, int duration) {
|
|
AnimElement data = {
|
|
x, y, // source x and y
|
|
w, h, // source width and height
|
|
method, // render method
|
|
0, // animStep
|
|
0, // animStepMax
|
|
0, // timeBase
|
|
delay, // delay before rendering begins
|
|
duration, // total animation time
|
|
nullptr, // storage for the source image
|
|
nullptr, // storage for the animation frame
|
|
Std::vector<AnimPlot>(),
|
|
false
|
|
}; // prescaled
|
|
_titles.push_back(data);
|
|
}
|
|
|
|
void IntroController::getTitleSourceData() {
|
|
uint r, g, b, a; // color values
|
|
byte *srcData; // plot data
|
|
const int BLUE[16] = {
|
|
255, 250, 226, 226, 210, 194, 161, 161,
|
|
129, 97, 97, 64, 64, 32, 32, 0
|
|
};
|
|
|
|
// The BKGD_INTRO image is assumed to have not been
|
|
// loaded yet. The unscaled version will be loaded
|
|
// here, and elements of the image will be stored
|
|
// individually. Afterward, the BKGD_INTRO image
|
|
// will be scaled appropriately.
|
|
ImageInfo *info = imageMgr->get(BKGD_INTRO, true);
|
|
if (!info)
|
|
error("ERROR 1007: Unable to load the image \"%s\"", BKGD_INTRO);
|
|
|
|
if (info->_width / info->_prescale != 320 || info->_height / info->_prescale != 200)
|
|
// The image appears to have been scaled already
|
|
warning("ERROR 1008: The title image (\"%s\") has been scaled too early", BKGD_INTRO);
|
|
|
|
// get the transparent color
|
|
_transparentColor = info->_image->getPaletteColor(_transparentIndex);
|
|
|
|
// turn alpha off, if necessary
|
|
bool alpha = info->_image->isAlphaOn();
|
|
info->_image->alphaOff();
|
|
|
|
// for each element, get the source data
|
|
for (unsigned i = 0; i < _titles.size(); i++) {
|
|
if ((_titles[i]._method != SIGNATURE) && (_titles[i]._method != BAR)) {
|
|
// create a place to store the source image
|
|
_titles[i]._srcImage = Image::create(
|
|
_titles[i]._rw * info->_prescale,
|
|
_titles[i]._rh * info->_prescale,
|
|
_titles[i]._method == MAP ? _mapScreen->format() : info->_image->format());
|
|
if (_titles[i]._srcImage->isIndexed())
|
|
_titles[i]._srcImage->setPaletteFromImage(info->_image);
|
|
|
|
// get the source image
|
|
info->_image->drawSubRectOn(
|
|
_titles[i]._srcImage,
|
|
0,
|
|
0,
|
|
_titles[i]._rx * info->_prescale,
|
|
_titles[i]._ry * info->_prescale,
|
|
_titles[i]._rw * info->_prescale,
|
|
_titles[i]._rh * info->_prescale);
|
|
}
|
|
|
|
// after getting the srcImage
|
|
switch (_titles[i]._method) {
|
|
case SIGNATURE: {
|
|
// PLOT: "Lord British"
|
|
srcData = getSigData();
|
|
RGBA color = info->_image->setColor(0, 255, 255); // cyan for EGA
|
|
int x = 0, y = 0;
|
|
|
|
while (srcData[_titles[i]._animStepMax] != 0) {
|
|
x = srcData[_titles[i]._animStepMax] - 0x4C;
|
|
y = 0xC0 - srcData[_titles[i]._animStepMax + 1];
|
|
|
|
if (settings._videoType != "EGA") {
|
|
// yellow gradient
|
|
color = info->_image->setColor(255, (y == 2 ? 250 : 255), BLUE[y - 1]);
|
|
}
|
|
AnimPlot plot = {
|
|
(uint8)x,
|
|
(uint8)y,
|
|
(uint8)color.r,
|
|
(uint8)color.g,
|
|
(uint8)color.b,
|
|
255
|
|
};
|
|
_titles[i]._plotData.push_back(plot);
|
|
_titles[i]._animStepMax += 2;
|
|
}
|
|
_titles[i]._animStepMax = _titles[i]._plotData.size();
|
|
break;
|
|
}
|
|
|
|
case BAR: {
|
|
_titles[i]._animStepMax = _titles[i]._rw; // image width
|
|
break;
|
|
}
|
|
|
|
case TITLE: {
|
|
for (int y = 0; y < _titles[i]._rh; y++) {
|
|
for (int x = 0; x < _titles[i]._rw ; x++) {
|
|
_titles[i]._srcImage->getPixel(x * info->_prescale, y * info->_prescale, r, g, b, a);
|
|
if (r || g || b) {
|
|
AnimPlot plot = { (uint8)(x + 1), (uint8)(y + 1), (uint8)r, (uint8)g, (uint8)b, (uint8)a };
|
|
_titles[i]._plotData.push_back(plot);
|
|
}
|
|
}
|
|
}
|
|
_titles[i]._animStepMax = _titles[i]._plotData.size();
|
|
break;
|
|
}
|
|
|
|
case MAP: {
|
|
// fill the map area with the transparent color
|
|
_titles[i]._srcImage->fillRect(
|
|
8, 8, 304, 80,
|
|
_transparentColor.r,
|
|
_transparentColor.g,
|
|
_transparentColor.b);
|
|
|
|
Image *scaled; // the scaled and filtered image
|
|
scaled = g_screen->screenScale(_titles[i]._srcImage, settings._scale / info->_prescale, 1, 1);
|
|
if (_transparentIndex >= 0)
|
|
scaled->setTransparentIndex(_transparentIndex);
|
|
|
|
_titles[i]._prescaled = true;
|
|
delete _titles[i]._srcImage;
|
|
_titles[i]._srcImage = scaled;
|
|
|
|
_titles[i]._animStepMax = 20;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
_titles[i]._animStepMax = _titles[i]._rh ; // image height
|
|
break;
|
|
}
|
|
}
|
|
|
|
// permanently disable alpha
|
|
if (_titles[i]._srcImage)
|
|
_titles[i]._srcImage->alphaOff();
|
|
|
|
// create the initial animation frame
|
|
_titles[i]._destImage = Image::create(
|
|
2 + (_titles[i]._prescaled ? SCALED(_titles[i]._rw) : _titles[i]._rw) * info->_prescale ,
|
|
2 + (_titles[i]._prescaled ? SCALED(_titles[i]._rh) : _titles[i]._rh) * info->_prescale,
|
|
_titles[i]._method == MAP ? _mapScreen->format() : info->_image->format());
|
|
if (_titles[i]._destImage->isIndexed())
|
|
_titles[i]._destImage->setPaletteFromImage(info->_image);
|
|
}
|
|
|
|
// turn alpha back on
|
|
if (alpha) {
|
|
info->_image->alphaOn();
|
|
}
|
|
|
|
// scale the original image now
|
|
Image *scaled = g_screen->screenScale(info->_image,
|
|
settings._scale / info->_prescale,
|
|
info->_image->isIndexed(),
|
|
1);
|
|
delete info->_image;
|
|
info->_image = scaled;
|
|
}
|
|
|
|
bool IntroController::updateTitle() {
|
|
#ifdef IOS_ULTIMA4
|
|
static bool firstTime = true;
|
|
if (firstTime) {
|
|
firstTime = false;
|
|
startTicks();
|
|
}
|
|
#endif
|
|
|
|
int animStepTarget = 0;
|
|
|
|
uint32 timeCurrent = g_system->getMillis();
|
|
float timePercent = 0;
|
|
|
|
if (_title->_animStep == 0 && !_bSkipTitles) {
|
|
if (_title->_timeBase == 0) {
|
|
// reset the base time
|
|
_title->_timeBase = timeCurrent;
|
|
}
|
|
if (_title == _titles.begin()) {
|
|
// clear the screen
|
|
Image *screen = imageMgr->get("screen")->_image;
|
|
screen->fillRect(0, 0, screen->width(), screen->height(), 0, 0, 0);
|
|
}
|
|
if (_title->_method == TITLE) {
|
|
// assume this is the first frame of "Ultima IV" and begin sound
|
|
soundPlay(SOUND_TITLE_FADE);
|
|
}
|
|
}
|
|
|
|
// abort after processing all elements
|
|
if (_title == _titles.end()) {
|
|
return false;
|
|
}
|
|
|
|
// delay the drawing of this phase
|
|
if ((timeCurrent - _title->_timeBase) < _title->_timeDelay) {
|
|
return true;
|
|
}
|
|
|
|
// determine how much of the animation should have been drawn up until now
|
|
timePercent = float(timeCurrent - _title->_timeBase - _title->_timeDelay) / _title->_timeDuration;
|
|
if (timePercent > 1 || _bSkipTitles)
|
|
timePercent = 1;
|
|
animStepTarget = int(_title->_animStepMax * timePercent);
|
|
|
|
// perform the animation
|
|
switch (_title->_method) {
|
|
case SIGNATURE: {
|
|
for (; animStepTarget > _title->_animStep; _title->_animStep++) {
|
|
// blit the pixel-pair to the src surface
|
|
_title->_destImage->fillRect(
|
|
_title->_plotData[_title->_animStep].x,
|
|
_title->_plotData[_title->_animStep].y,
|
|
2,
|
|
1,
|
|
_title->_plotData[_title->_animStep].r,
|
|
_title->_plotData[_title->_animStep].g,
|
|
_title->_plotData[_title->_animStep].b);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case BAR: {
|
|
RGBA color;
|
|
while (animStepTarget > _title->_animStep) {
|
|
_title->_animStep++;
|
|
color = _title->_destImage->setColor(128, 0, 0); // dark red for the underline
|
|
|
|
// blit bar to the canvas
|
|
_title->_destImage->fillRect(
|
|
1,
|
|
1,
|
|
_title->_animStep,
|
|
1,
|
|
color.r,
|
|
color.g,
|
|
color.b);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AND: {
|
|
// blit the entire src to the canvas
|
|
_title->_srcImage->drawOn(_title->_destImage, 1, 1);
|
|
_title->_animStep = _title->_animStepMax;
|
|
break;
|
|
}
|
|
|
|
case ORIGIN: {
|
|
if (_bSkipTitles)
|
|
_title->_animStep = _title->_animStepMax;
|
|
else {
|
|
_title->_animStep++;
|
|
_title->_timeDelay = g_system->getMillis() - _title->_timeBase + 100;
|
|
}
|
|
|
|
// blit src to the canvas one row at a time, bottom up
|
|
_title->_srcImage->drawSubRectOn(
|
|
_title->_destImage,
|
|
1,
|
|
_title->_destImage->height() - 1 - _title->_animStep,
|
|
0,
|
|
0,
|
|
_title->_srcImage->width(),
|
|
_title->_animStep);
|
|
break;
|
|
}
|
|
|
|
case PRESENT: {
|
|
if (_bSkipTitles)
|
|
_title->_animStep = _title->_animStepMax;
|
|
else {
|
|
_title->_animStep++;
|
|
_title->_timeDelay = g_system->getMillis() - _title->_timeBase + 100;
|
|
}
|
|
|
|
// blit src to the canvas one row at a time, top down
|
|
_title->_srcImage->drawSubRectOn(
|
|
_title->_destImage,
|
|
1,
|
|
1,
|
|
0,
|
|
_title->_srcImage->height() - _title->_animStep,
|
|
_title->_srcImage->width(),
|
|
_title->_animStep);
|
|
break;
|
|
}
|
|
|
|
case TITLE: {
|
|
// blit src to the canvas in a random pixelized manner
|
|
_title->_animStep = animStepTarget;
|
|
|
|
_title->shufflePlotData();
|
|
|
|
_title->_destImage->fillRect(1, 1, _title->_rw, _title->_rh, 0, 0, 0);
|
|
|
|
// @TODO: animStepTarget (for this loop) should not exceed
|
|
// half of animStepMax. If so, instead draw the entire
|
|
// image, and then black out the necessary pixels.
|
|
// this should speed the loop up at the end
|
|
for (int i = 0; i < animStepTarget; ++i) {
|
|
_title->_destImage->putPixel(
|
|
_title->_plotData[i].x,
|
|
_title->_plotData[i].y,
|
|
_title->_plotData[i].r,
|
|
_title->_plotData[i].g,
|
|
_title->_plotData[i].b,
|
|
_title->_plotData[i].a);
|
|
}
|
|
|
|
// cover the "present" area with the transparent color
|
|
_title->_destImage->fillRect(
|
|
75, 1, 54, 5,
|
|
_transparentColor.r,
|
|
_transparentColor.g,
|
|
_transparentColor.b);
|
|
break;
|
|
}
|
|
|
|
case SUBTITLE: {
|
|
if (_bSkipTitles)
|
|
_title->_animStep = _title->_animStepMax;
|
|
else {
|
|
_title->_animStep++;
|
|
_title->_timeDelay = g_system->getMillis() - _title->_timeBase + 100;
|
|
}
|
|
|
|
// blit src to the canvas one row at a time, center out
|
|
int y = int(_title->_rh / 2) - _title->_animStep + 1;
|
|
_title->_srcImage->drawSubRectOn(
|
|
_title->_destImage,
|
|
1,
|
|
y + 1,
|
|
0,
|
|
y,
|
|
_title->_srcImage->width(),
|
|
1 + ((_title->_animStep - 1) * 2));
|
|
break;
|
|
}
|
|
|
|
case MAP: {
|
|
if (_bSkipTitles)
|
|
_title->_animStep = _title->_animStepMax;
|
|
else {
|
|
_title->_animStep++;
|
|
_title->_timeDelay = g_system->getMillis() - _title->_timeBase + 100;
|
|
}
|
|
|
|
int step = (_title->_animStep == _title->_animStepMax ? _title->_animStepMax - 1 : _title->_animStep);
|
|
|
|
// blit src to the canvas one row at a time, center out
|
|
_title->_srcImage->drawSubRectOn(
|
|
_title->_destImage,
|
|
SCALED(153 - (step * 8)),
|
|
SCALED(1),
|
|
0,
|
|
0,
|
|
SCALED((step + 1) * 8),
|
|
SCALED(_title->_srcImage->height()));
|
|
_title->_srcImage->drawSubRectOn(
|
|
_title->_destImage,
|
|
SCALED(161),
|
|
SCALED(1),
|
|
SCALED(312 - (step * 8)),
|
|
0,
|
|
SCALED((step + 1) * 8),
|
|
SCALED(_title->_srcImage->height()));
|
|
|
|
|
|
// Create a destimage for the map tiles
|
|
int newtime = g_system->getMillis();
|
|
if (newtime > _title->_timeDuration + 250 / 4) {
|
|
// Draw the updated map display
|
|
drawMapStatic();
|
|
|
|
_mapScreen->drawSubRectOn(
|
|
_title->_srcImage,
|
|
SCALED(8),
|
|
SCALED(8),
|
|
SCALED(8),
|
|
SCALED(13 * 8),
|
|
SCALED(38 * 8),
|
|
SCALED(10 * 8));
|
|
|
|
_title->_timeDuration = newtime + 250 / 4;
|
|
}
|
|
|
|
_title->_srcImage->drawSubRectOn(
|
|
_title->_destImage,
|
|
SCALED(161 - (step * 8)),
|
|
SCALED(9),
|
|
SCALED(160 - (step * 8)),
|
|
SCALED(8),
|
|
SCALED((step * 2) * 8),
|
|
SCALED((10 * 8)));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// draw the titles
|
|
drawTitle();
|
|
|
|
// if the animation for this title has completed,
|
|
// move on to the next title
|
|
if (_title->_animStep >= _title->_animStepMax) {
|
|
// free memory that is no longer needed
|
|
compactTitle();
|
|
_title++;
|
|
|
|
if (_title == _titles.end()) {
|
|
// reset the timer to the pre-titles granularity
|
|
eventHandler->getTimer()->reset(settings._eventTimerGranularity);
|
|
|
|
// make sure the titles only appear when the app first loads
|
|
_bSkipTitles = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (_title->_method == TITLE) {
|
|
// assume this is "Ultima IV" and pre-load sound
|
|
// soundLoad(SOUND_TITLE_FADE);
|
|
eventHandler->getTimer()->reset(settings._titleSpeedRandom);
|
|
} else if (_title->_method == MAP) {
|
|
eventHandler->getTimer()->reset(settings._titleSpeedOther);
|
|
} else {
|
|
eventHandler->getTimer()->reset(settings._titleSpeedOther);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void IntroController::compactTitle() {
|
|
if (_title->_srcImage) {
|
|
delete _title->_srcImage;
|
|
_title->_srcImage = nullptr;
|
|
}
|
|
_title->_plotData.clear();
|
|
}
|
|
|
|
void IntroController::drawTitle() {
|
|
Image *scaled; // the scaled and filtered image
|
|
|
|
// blit the scaled and filtered surface to the screen
|
|
if (_title->_prescaled)
|
|
scaled = _title->_destImage;
|
|
else
|
|
scaled = g_screen->screenScale(_title->_destImage, settings._scale, 1, 1);
|
|
|
|
scaled->setTransparentIndex(_transparentIndex);
|
|
scaled->drawSubRect(
|
|
SCALED(_title->_rx), // dest x, y
|
|
SCALED(_title->_ry),
|
|
SCALED(1), // src x, y, w, h
|
|
SCALED(1),
|
|
SCALED(_title->_rw),
|
|
SCALED(_title->_rh));
|
|
|
|
if (!_title->_prescaled) {
|
|
delete scaled;
|
|
scaled = nullptr;
|
|
}
|
|
}
|
|
|
|
void IntroController::skipTitles() {
|
|
_bSkipTitles = true;
|
|
soundStop();
|
|
}
|
|
|
|
#ifdef IOS_ULTIMA4
|
|
void IntroController::tryTriggerIntroMusic() {
|
|
if (mode == INTRO_MAP)
|
|
g_music->intro();
|
|
}
|
|
#endif
|
|
|
|
} // End of namespace Ultima4
|
|
} // End of namespace Ultima
|