scummvm/engines/groovie/logic/tlcgame.cpp
2024-08-07 22:06:14 +03:00

1273 lines
35 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, MojoTouch has exclusively licensed
* this code on November 10th, 2021, to be use in closed-source products.
* Therefore, any contributions (commits) to it will also be dual-licensed.
*
*/
#include "groovie/logic/tlcgame.h"
#include "groovie/groovie.h"
#include "common/archive.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/file.h"
#include "common/macresman.h"
#include "common/util.h"
namespace Groovie {
// This a list of files for background music. This list is hardcoded in the TLC player.
const char *kTlcMusicFiles[] = {
"ep01epm", "ep01tatm", "amb_hs", "amb_mr", "amb_kr",
"amb_mo", "music_rc", "amb_ds", "amb_ds3", "amb_jr",
"amb_mr4", "amb_jr4", "amb_jr2", "amb_kr2", "amb_mr2",
"amb_br", "amb_ds2", "amb_jr3", "amb_ds4", "amb_kr3",
"amb_to1", "amb_to2", "ep02epm", "ep02tatm", "ep03epm",
"ep03tatm", "ep04epm", "ep04tatm", "ep05epm", "ep05tatm",
"ep06epm", "ep06tatm", "ep07epm", "ep07tatm", "ep08epm",
"ep08tatm", "ep09epm", "ep09tatm", "ep10epm", "ep10tatm",
"ep11epm", "ep11tatm", "ep12epm", "ep12tatm", "ep13epm",
"ep13tatm", "ep14epm", "ep14tatm", "ep15epm", "ep15tatm"
};
const uint8 kTlcEpQuestToPlay[] = {
0x0E, 0x0F, 0x0B, 0x10, 0x11,
0x12, 0x0C, 0x0C, 0x09, 0x06,
0x0F, 0x0C, 0x0B, 0x0D, 0x0D
};
TlcGame::TlcGame(byte *scriptVariables) :
_numRegionHeaders(0), _regionHeader(NULL), _curQuestNumAnswers(-1), _epQuestionsData(NULL),
_random("GroovieTlcGame"), _scriptVariables(scriptVariables),
_tatHeaders(NULL), _tatQuestions(NULL), _curQuestRegions(), _epScoreBin(), _tatFlags() {
_curAnswerIndex = 0;
_epEpisodeIdx = 0;
_epQuestionIdx = 0;
_epQuestionNumOfPool = -1;
_epQuestionsInEpisode = 0;
_tatEpisodes = 0;
_tatQuestCount = 0;
}
TlcGame::~TlcGame() {
delete[] _regionHeader;
delete[] _epQuestionsData;
delete[] _tatHeaders;
delete[] _tatQuestions;
}
void TlcGame::handleOp(uint8 op) {
switch (op) {
case 0:
debugC(1, kDebugScript, "Groovie::Script: Op42 (0x%02X): TLC Regions", op);
opRegions();
break;
case 1:
debugC(1, kDebugScript, "Groovie::Script: Op42 (0x%02X): TLC Exit Polls", op);
opExitPoll();
break;
case 2:
debugC(1, kDebugScript, "Groovie::Script: Op42 (0x%02X): TLC TATFlags", op);
opFlags();
break;
case 3:
debugC(1, kDebugScript, "Groovie::Script: Op42 (0x%02X): TLC TATs (TODO)", op);
opTat();
break;
default:
debugC(1, kDebugScript, "Groovie::Script: Op42 (0x%02X): TLC Invalid -> NOP", op);
}
}
// This function is mainly for debugging purpose
void inline TlcGame::setScriptVar(uint16 var, byte value) {
_scriptVariables[var] = value;
debugC(5, kDebugLogic, "script variable[0x%03X] = %d (0x%04X)", var, value, value);
}
void inline TlcGame::setScriptVar16(uint16 var, uint16 value) {
_scriptVariables[var] = value & 0xFF;
_scriptVariables[var + 1] = (value >> 8) & 0xFF;
debugC(5, kDebugLogic, "script variable[0x%03X, 0x%03X] = %d (0x%02X, 0x%02X)", var, var+1, value, _scriptVariables[var], _scriptVariables[var+1]);
}
uint16 inline TlcGame::getScriptVar16(uint16 var) {
uint16 value;
value = _scriptVariables[var];
value += _scriptVariables[var + 1] << 8;
return value;
}
// Gets the filename of the background music file.
const char *TlcGame::getTlcMusicFilename(int musicId) {
return kTlcMusicFiles[musicId];
}
void TlcGame::opRegions() {
if (_scriptVariables[0x1A] == 1) {
regionsInit();
setScriptVar(0x1A, 0);
} else {
regionsLoad();
}
}
// Loads the header of the file regions.rle into memory. This files contains
// a database for the GUI positions (regions) of all answers for the questions.
void TlcGame::regionsInit() {
Common::SeekableReadStream *regionsfile = 0;
_curQuestNumAnswers = -1;
// Check if header was already loaded.
if (_regionHeader != NULL) {
debugC(1, kDebugLogic, "TLC:RegionsInit: Regions already loaded.");
return;
}
regionsfile = SearchMan.createReadStreamForMember("SYSTEM/REGIONS.RLE");
if (!regionsfile) {
error("TLC:RegionsInit: Could not open 'SYSTEM/REGIONS.RLE'");
}
// Read number of question entries
_numRegionHeaders = regionsfile->readUint32LE();
if (regionsfile->eos()) {
error("TLC:RegionsInit: Error reading numEntries from 'REGIONS.RLE'");
}
// Read header for each question entry
_regionHeader = new TlcRegionsHeader[_numRegionHeaders];
for (int i = 0; i < _numRegionHeaders; i++) {
regionsfile->read(_regionHeader[i].name, sizeof(TlcRegionsHeader::name));
regionsfile->seek(25 - sizeof(TlcRegionsHeader::name), SEEK_CUR);
_regionHeader[i].numAnswers = regionsfile->readUint32LE();
_regionHeader[i].offset = regionsfile->readUint32LE();
}
if (regionsfile->eos()) {
error("TLC:RegionsInit: Error reading headers from 'REGIONS.RLE'");
}
delete regionsfile;
debugC(1, kDebugLogic, "TLC:RegionsInit: Loaded %d region headers", _numRegionHeaders);
}
// Loads the specific regions for one questions.
void TlcGame::regionsLoad() {
// Check if initRegions was called before
if (_regionHeader == NULL) {
error("TLC:RegionsLoad: initRegions was not called.");
}
// Open regions.rle
Common::SeekableReadStream *regionsfile = SearchMan.createReadStreamForMember("SYSTEM/REGIONS.RLE");
if (!regionsfile) {
error("TLC:RegionsLoad: Could not open 'SYSTEM/REGIONS.RLE'");
}
// Get length of question name from variables
int nameLen = _scriptVariables[0x1B] * 10 + _scriptVariables[0x1C];
if (nameLen >= ARRAYSIZE(TlcRegionsHeader::name)) {
error("TLC:RegionsLoad: Name to long for loadRegions!");
}
// Decoded and copy name from variables
char questName[sizeof(TlcRegionsHeader::name)];
for (int i = 0; i < nameLen; i++) {
setScriptVar(0x1D + i, _scriptVariables[0x1D + i] + 0x30);
questName[i] = _scriptVariables[0x1D + i];
}
questName[nameLen] = '\0';
// Search for the question entry
for (int i = 0; i <= _numRegionHeaders; i++) {
Common::String regionName(_regionHeader[i].name);
if (regionName.equalsIgnoreCase(questName)) {
// move to coordinates for this question
regionsfile->seek(_regionHeader[i].offset, SEEK_SET);
// Copy region of each answer
_curQuestNumAnswers = _regionHeader[i].numAnswers;
for (int iAns = 0; iAns < _curQuestNumAnswers; iAns++) {
_curQuestRegions[iAns].left = regionsfile->readUint16LE();
_curQuestRegions[iAns].top = regionsfile->readUint16LE();
_curQuestRegions[iAns].right = regionsfile->readUint16LE();
_curQuestRegions[iAns].bottom = regionsfile->readUint16LE();
}
delete regionsfile;
debugC(1, kDebugLogic, "TLC:RegionsLoad: Loaded %d regions for question %s", _curQuestNumAnswers, questName);
return;
}
}
// If we got here, the entry was not found
error("TLC:RegionsLoad: Question '%s' was not found", questName);
delete regionsfile; // unreachable
}
void TlcGame::getRegionRewind() {
_curAnswerIndex = 0;
}
int TlcGame::getRegionNext(uint16 &left, uint16 &top, uint16 &right, uint16 &bottom) {
// Check if initialization was done
if (_curQuestNumAnswers < 0) {
warning("TLC:GetRegionNext: Uninitialized call to getRegionNext.");
return -1;
}
// Check if there is another region for this answer
if (_curAnswerIndex >= _curQuestNumAnswers) {
return -1;
}
// return next region
left = _curQuestRegions[_curAnswerIndex].left;
top = _curQuestRegions[_curAnswerIndex].top;
right = _curQuestRegions[_curAnswerIndex].right;
bottom = _curQuestRegions[_curAnswerIndex].bottom;
_curAnswerIndex++;
return 0;
}
void TlcGame::opExitPoll() {
switch (_scriptVariables[0]) {
case 0x00:
epInit();
break;
case 0x01:
epSelectNextQuestion();
break;
case 0x02:
epResultQuestion();
break;
case 0x03:
epResultEpisode();
break;
case 0x04:
// Load internal score-bin 4 and 5
_epScoreBin[4] = _scriptVariables[1];
_epScoreBin[5] = _scriptVariables[2];
setScriptVar(0, 0x09);
debugC(1, kDebugLogic, "TLC:EpInitBins: Init bins: bin[4]=%d, bin[5]=%d", _epScoreBin[4], _epScoreBin[5]);
break;
default:
// Unknown subcommand
debugC(0, kDebugLogic, "TLC:opExitPoll: Unknown subcommand=%d", _scriptVariables[0]);
setScriptVar(0, 0x08);
}
}
void TlcGame::epInit() {
Common::SeekableReadStream *epaidbfile = 0;
int i;
int numEpisodesInDB;
uint32 scoreDataOffset;
// Delete previous question data
delete[] _epQuestionsData;
// Get current episode from script variables
_epEpisodeIdx = _scriptVariables[0x01] - 0x31;
_epQuestionIdx = 0;
// Init score bin 0..3 of overall 6
_epScoreBin[0] = 0;
_epScoreBin[1] = 0;
_epScoreBin[2] = 0;
_epScoreBin[3] = 0;
if (_epEpisodeIdx == 0) {
_epScoreBin[4] = 0;
_epScoreBin[5] = 0;
}
// Open epaidb.rle
epaidbfile = SearchMan.createReadStreamForMember("SYSTEM/EPAIDB.RLE");
if (!epaidbfile) {
error("TLC:EpInit: Could not open 'SYSTEM/EPAIDB.RLE'");
}
// Read number of episodes. It is not dynamic because the result functions highly depend on the database
// Thus we knew, we must check the result functions, if this would change (most propably not)
numEpisodesInDB = epaidbfile->readSint32LE();
if (numEpisodesInDB != GROOVIE_TLC_MAX_EPSIODES) {
error("TLC:EpInit: Unexpected number of episodes in epaidb.rle. Read: %d, expected: %d", numEpisodesInDB, GROOVIE_TLC_MAX_EPSIODES);
}
// read header of ep score data of current episode TO
if (_epEpisodeIdx < 0 || _epEpisodeIdx >= numEpisodesInDB) {
error("TLC:EpInit: Requested episode out of range (0..%d)", GROOVIE_TLC_MAX_EPSIODES-1);
}
epaidbfile->seek((_epEpisodeIdx * 8) + 4, SEEK_SET);
_epQuestionsInEpisode = epaidbfile->readSint32LE();
scoreDataOffset = epaidbfile->readSint32LE();
// Load scores for this episode.
_epQuestionsData = new TlcEpQuestionData[_epQuestionsInEpisode];
epaidbfile->seek(scoreDataOffset, SEEK_SET);
for (i = 0; i < _epQuestionsInEpisode; i++) {
_epQuestionsData[i].questionUsed = false;
_epQuestionsData[i].questionScore = epaidbfile->readUint32LE();
}
if (epaidbfile->eos()) {
error("TLC:EpInit: Error reading scores from 'EPAIDB.RLE'");
}
// Close file
delete epaidbfile;
// Initialize register 0x01 with values from database
if (_epEpisodeIdx >= ARRAYSIZE(kTlcEpQuestToPlay)) {
error("TLC:EpInit: EposdeIdx out of range for init data of reg0x01");
}
setScriptVar(2, kTlcEpQuestToPlay[_epEpisodeIdx] + 0x30);
// Return code
setScriptVar(0, 0x09);
debugC(1, kDebugLogic, "TLC:EpInit: For episode %d loaded %d question scores. Will play %d questions", _epEpisodeIdx+1, _epQuestionsInEpisode, kTlcEpQuestToPlay[_epEpisodeIdx]);
}
void TlcGame::epSelectNextQuestion() {
_epQuestionIdx++;
_epQuestionNumOfPool = -1;
/* check if there is a dedicated quesition at this position */
switch (_epEpisodeIdx) { // _epEpsiodeIdx: 0..14
case 1:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 5; break;
case 7: _epQuestionNumOfPool = 16; break;
case 11: _epQuestionNumOfPool = 18; break;
}
break;
case 2:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 7; break;
case 7: _epQuestionNumOfPool = 13; break;
case 11: _epQuestionNumOfPool = 16; break;
}
break;
case 3:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 8; break;
case 11: _epQuestionNumOfPool = 17; break;
}
break;
case 4:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 18; break;
case 7: _epQuestionNumOfPool = 22; break;
case 11: _epQuestionNumOfPool = 21; break;
}
break;
case 5:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 22; break;
case 11: _epQuestionNumOfPool = 24; break;
}
break;
case 6:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 12; break;
case 7: _epQuestionNumOfPool = 14; break;
case 11: _epQuestionNumOfPool = 18; break;
}
break;
case 7:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 15; break;
case 7: _epQuestionNumOfPool = 16; break;
case 11: _epQuestionNumOfPool = 17; break;
}
break;
case 8:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 11; break;
case 7: _epQuestionNumOfPool = 14; break;
case 9: _epQuestionNumOfPool = 15; break;
}
break;
case 9:
switch (_epQuestionIdx) {
case 2: _epQuestionNumOfPool = 2; break;
case 4: _epQuestionNumOfPool = 8; break;
case 6: _epQuestionNumOfPool = 11; break;
}
break;
case 10:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 15; break;
case 7: _epQuestionNumOfPool = 16; break;
case 11: _epQuestionNumOfPool = 17; break;
}
break;
case 11:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 14; break;
case 7: _epQuestionNumOfPool = 15; break;
case 11: _epQuestionNumOfPool = 17; break;
}
break;
case 12:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 12; break;
case 7: _epQuestionNumOfPool = 14; break;
case 11: _epQuestionNumOfPool = 16; break;
}
break;
case 13:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 15; break;
case 7: _epQuestionNumOfPool = 18; break;
case 11: _epQuestionNumOfPool = 19; break;
}
break;
case 14:
switch (_epQuestionIdx) {
case 3: _epQuestionNumOfPool = 1; break;
case 7: _epQuestionNumOfPool = 14; break;
case 11: _epQuestionNumOfPool = 16; break;
}
break;
}
// get a random question if there was no predefined
if (_epQuestionNumOfPool == -1) {
do {
_epQuestionNumOfPool = _random.getRandomNumber(32767) / 2000;
} while (_epQuestionNumOfPool < 1 || _epQuestionNumOfPool > _epQuestionsInEpisode);
debugC(1, kDebugLogic, "TLC:EpSelNextQuest: Question %d: Selected question %d/%d by random.", _epQuestionIdx, _epQuestionNumOfPool, _epQuestionsInEpisode);
} else {
debugC(1, kDebugLogic, "TLC:EpSelNextQuest: Question %d: Selected question %d/%d by predefined data.", _epQuestionIdx, _epQuestionNumOfPool, _epQuestionsInEpisode);
}
// Choose next question, if question was already played
while (_epQuestionsData[_epQuestionNumOfPool - 1].questionUsed) {
_epQuestionNumOfPool++;
if (_epQuestionNumOfPool > _epQuestionsInEpisode) {
_epQuestionNumOfPool = 1;
}
}
_epQuestionsData[_epQuestionNumOfPool - 1].questionUsed = true;
debugC(1, kDebugLogic, "TLC:EpSelNextQuest: Question %d: Forward to question %d/%d. (used-flag)", _epQuestionIdx, _epQuestionNumOfPool, _epQuestionsInEpisode);
// write selected episode and question to script variables
setScriptVar(4, (_epEpisodeIdx + 1) / 10);
setScriptVar(5, (_epEpisodeIdx + 1) % 10);
setScriptVar(6, _epQuestionNumOfPool / 10);
setScriptVar(7, _epQuestionNumOfPool % 10);
// Set return value
setScriptVar(0, 9);
// Debug output
{
uint32 dbgQScore = _epQuestionsData[_epQuestionNumOfPool - 1].questionScore;
debugC(1, kDebugLogic, "TLC:EpSelNextQuest: Bins for Answers: %d %d %d %d %d %d %d %d",
(dbgQScore >> 28) & 0xf, (dbgQScore >> 24) & 0xf, (dbgQScore >> 20) & 0xf, (dbgQScore >> 16) & 0xf,
(dbgQScore >> 12) & 0xf, (dbgQScore >> 8) & 0xf, (dbgQScore >> 4) & 0xf, (dbgQScore) & 0xf);
}
}
void TlcGame::epResultQuestion() {
int answerIdx, shift, scoreBinId;
int specialReg;
uint32 questionScore;
// Add special question results here
specialReg = -1;
switch (_epEpisodeIdx) { // _epEpsiodeIdx: 0..14
case 1:
switch (_epQuestionNumOfPool) {
case 5: specialReg = 0x09; break;
case 16: specialReg = 0x0A; break;
case 18: specialReg = 0x0B; break;
}
break;
case 2:
switch (_epQuestionNumOfPool) {
case 7: specialReg = 0x09; break;
case 13: specialReg = 0x0A; break;
case 16: specialReg = 0x0B; break;
}
break;
case 3:
switch (_epQuestionNumOfPool) {
case 8: specialReg = 0x09; break;
case 17: specialReg = 0x0A; break;
}
break;
case 4:
switch (_epQuestionNumOfPool) {
case 18: specialReg = 0x09; break;
case 22: specialReg = 0x0A; break;
case 21: specialReg = 0x0B; break;
}
break;
case 5:
switch (_epQuestionNumOfPool) {
case 22: specialReg = 0x09; break;
case 24: specialReg = 0x0A; break;
}
break;
case 6:
switch (_epQuestionNumOfPool) {
case 12: specialReg = 0x09; break;
case 14: specialReg = 0x0A; break;
case 18: specialReg = 0x0B; break;
}
break;
case 7:
switch (_epQuestionNumOfPool) {
case 15: specialReg = 0x09; break;
case 16: specialReg = 0x0A; break;
case 17: specialReg = 0x0B; break;
}
break;
case 8:
switch (_epQuestionNumOfPool) {
case 11: specialReg = 0x09; break;
case 14: specialReg = 0x0A; break;
case 15: specialReg = 0x0B; break;
}
break;
case 9:
switch (_epQuestionNumOfPool) {
case 2: specialReg = 0x09; break;
case 8: specialReg = 0x0A; break;
case 11: specialReg = 0x0B; break;
}
break;
case 10:
switch (_epQuestionNumOfPool) {
case 15: specialReg = 0x09; break;
case 16: specialReg = 0x0A; break;
case 17: specialReg = 0x0B; break;
}
break;
case 11:
switch (_epQuestionNumOfPool) {
case 14: specialReg = 0x09; break;
case 15: specialReg = 0x0A; break;
case 17: specialReg = 0x0B; break;
}
break;
case 12:
switch (_epQuestionNumOfPool) {
case 12: specialReg = 0x09; break;
case 14: specialReg = 0x0A; break;
case 16: specialReg = 0x0B; break;
}
break;
case 13:
switch (_epQuestionNumOfPool) {
case 15: specialReg = 0x09; break;
case 18: specialReg = 0x0A; break;
case 19: specialReg = 0x0B; break;
}
break;
case 14:
switch (_epQuestionNumOfPool) {
case 1: specialReg = 0x09; break;
case 14: specialReg = 0x0A; break;
case 16: specialReg = 0x0B; break;
}
break;
}
// Add value of register 3 (answer register) to spezial register
if (specialReg >= 0) {
setScriptVar(specialReg, _scriptVariables[specialReg] + _scriptVariables[3]);
debugC(1, kDebugLogic, "TLC:EpResultQuest: Question: %d vars[0x%02x] += %d. New Value: %d", _epQuestionIdx, specialReg, _scriptVariables[3], _scriptVariables[specialReg]);
}
// Process info from score database
answerIdx = _scriptVariables[3];
shift = (7 - answerIdx) * 4;
questionScore = _epQuestionsData[_epQuestionNumOfPool - 1].questionScore;
scoreBinId = (questionScore >> shift) & 0xF;
if (scoreBinId > 5) {
error("TLC:EpResultQuest: Invalid score bin %d (0..5 allowed)", scoreBinId);
}
_epScoreBin[scoreBinId] = _epScoreBin[scoreBinId] + 1;
debugC(1, kDebugLogic, "TLC:EpResultQuest: Answer: %d -> Inc bin[%d] -> bin[0..5] = %d, %d, %d, %d, %d, %d",
answerIdx+1, scoreBinId, _epScoreBin[0], _epScoreBin[1], _epScoreBin[2], _epScoreBin[3], _epScoreBin[4], _epScoreBin[5]);
}
/*
* Processes the result of the questions for this episode.
* _epScoreBin[ 0]: Ignored. Used if this answer for a question has no influence. Reset with each new Exit Poll.
* _epScoreBin[1..3]: Seems to be used to select alternative video for this episode. Reset with each new Exit Poll.
* _epScoreBin[4..5]: Seems to be used over the whole game. (Values are kept over the episodes in the script variables.)
*/
void TlcGame::epResultEpisode() {
uint16 maxBinValue;
int i;
/* keep only the maxium scores of bin[1], bin[2], bin[3]. -> Set all other to 0 */
debugCN(1, kDebugLogic, "TLC:EpResultEpisode: bins[1..3] = %d, %d, %d ", _epScoreBin[1], _epScoreBin[2], _epScoreBin[3]);
maxBinValue = _epScoreBin[1];
for (i = 2; i < 4; i++) {
if (maxBinValue < _epScoreBin[i]) {
maxBinValue = _epScoreBin[i];
}
}
for (i = 1; i < 4; i++) {
if (_epScoreBin[i] < maxBinValue) {
_epScoreBin[i] = 0;
}
}
debugC(1, kDebugLogic, "-> bins[1..3] = %d, %d, %d ", _epScoreBin[1], _epScoreBin[2], _epScoreBin[3]);
/* Select next stream according to which bin(s) are still >0. */
if (_epScoreBin[1] != 0 && _epScoreBin[2] == 0 && _epScoreBin[3] == 0) {
setScriptVar(3, 1);
} else if (_epScoreBin[1] == 0 && _epScoreBin[2] != 0 && _epScoreBin[3] == 0) {
setScriptVar(3, 2);
} else if (_epScoreBin[1] == 0 && _epScoreBin[2] == 0 && _epScoreBin[3] != 0) {
setScriptVar(3, 3);
} else if (_epScoreBin[1] != 0 && _epScoreBin[2] != 0 && _epScoreBin[3] == 0) {
setScriptVar(3, _random.getRandomNumberRng(1, 2));
} else if (_epScoreBin[1] != 0 && _epScoreBin[2] == 0 && _epScoreBin[3] != 0) {
setScriptVar(3, (_random.getRandomNumberRng(0, 1) * 2) + 1);
} else if (_epScoreBin[1] == 0 && _epScoreBin[2] != 0 && _epScoreBin[3] != 0) {
setScriptVar(3, _random.getRandomNumberRng(2, 3));
} else if (_epScoreBin[1] != 0 && _epScoreBin[2] != 0 && _epScoreBin[3] != 0) {
setScriptVar(3, _random.getRandomNumberRng(1, 3));
} else {
error("Tlc:EpResultEpisode: Stream selection failed. bins[0..5] = %d, %d, %d, %d, %d, %d",
_epScoreBin[0], _epScoreBin[1], _epScoreBin[2], _epScoreBin[3], _epScoreBin[4], _epScoreBin[5]);
}
debugC(1, kDebugLogic, "Selected stream [1..3] = %d ", _scriptVariables[3]);
/* save bin values of bin[4..5] to script variables */
setScriptVar(1, _epScoreBin[4]);
setScriptVar(2, _epScoreBin[5]);
/* return values */
setScriptVar(0, 9);
}
void TlcGame::opFlags() {
int x;
int y;
switch (_scriptVariables[0]) {
// Initialize the flags all to 0. Done at the beginning of a new TAT
case 0x00:
for (x = 0; x < 0x0E; x++) {
for (y = 0; y < 0x09; y++) {
_tatFlags[x][y] = 0;
}
}
debugC(0, kDebugLogic, "Tlc:TatFlags: Initialized fields (%d, %d)", x, y);
break;
// Get and set flags
case 0x01:
// Calculate position in flag field
x = 10 * _scriptVariables[0x04] + _scriptVariables[0x05];
y = _scriptVariables[0x06];
if (x >= 0x0E) {
warning("Tlc:TatFlags: x=%d out of range (0...13).", x);
x = 0x0E;
}
if (y >= 0x09) {
warning("Tlc:TatFlags: y=%d out of range (0...8).", x);
x = 0x0E;
}
// Check flags in field
if (_tatFlags[x][y] == 0) {
setScriptVar(0x01, 0);
_tatFlags[x][y] = 1;
debugC(1, kDebugLogic, "Tlc:TatFlags: Set x=%d, y=%d to 1", x, y);
debugTatFlags(0, 1);
debugTatFlags(2, 3);
debugTatFlags(4, 5);
debugTatFlags(6, 7);
}
else {
setScriptVar(0x01, 1);
}
break;
}
}
void TlcGame::debugTatFlags(int y1, int y2) {
Common::String s1, s2;
for (int x = 0; x < 14; x++) {
s1 += Common::String::format("%d", _tatFlags[x][y1]);
s2 += Common::String::format("%d", _tatFlags[x][y2]);
}
debugC(0, kDebugLogic, "Tlc:TatFlags: %s %s", s1.c_str(), s2.c_str());
}
void TlcGame::opTat() {
switch (_scriptVariables[0x40]) {
case 1:
tatInitRegs();
setScriptVar(0x40, 0);
break;
case 2:
tatLoadDB();
setScriptVar(0x40, 0);
break;
case 3:
tatResultQuest();
setScriptVar(0x40, 0);
break;
case 4:
tatResultEpisode();
setScriptVar(0x40, 0);
break;
case 9:
tatGetProfile();
setScriptVar(0x40, 0);
break;
}
}
void TlcGame::tatInitRegs() {
for (int i = 0; i < 0x10; i++) {
setScriptVar(0x4D + i, 0);
setScriptVar16(0x5D + i*2, 0);
}
// TODO:
// memset(_tatUnkData0_14, 0, 15);
}
void TlcGame::tatLoadDBHeaders() {
// Load tat headers if not already done
if (_tatHeaders == NULL) {
// Open tataidb.rle
Common::SeekableReadStream *tataidbfile = SearchMan.createReadStreamForMember("SYSTEM/TATAIDB.RLE");
if (!tataidbfile) {
error("TLC:TatLoadDB: Could not open 'SYSTEM/TATAIDB.RLE'");
}
_tatEpisodes = tataidbfile->readUint32LE();
_tatHeaders = new TlcTatHeader[_tatEpisodes];
for (int iEpisode = 0; iEpisode < _tatEpisodes; iEpisode++) {
_tatHeaders[iEpisode].questionsNum = tataidbfile->readUint32LE();
_tatHeaders[iEpisode].questionsOffset = tataidbfile->readUint32LE();
for (int iBin = 0; iBin < 16; iBin++) {
_tatHeaders[iEpisode].binDividends[iBin] = tataidbfile->readByte();
}
}
if (tataidbfile->eos()) {
error("TLC:TatLoadDB: Error reading headers from 'TATAIDB.RLE'");
}
}
}
void TlcGame::tatLoadDB() {
Common::SeekableReadStream *tataidbfile = 0;
int episode;
uint32 questOffset;
for (int iBin = 0; iBin < 0x10; iBin++) {
setScriptVar(0x4D + iBin, 0);
}
// Load TAT headers
tatLoadDBHeaders();
// Load questions for the requested episode
episode = _scriptVariables[0x47] - 0x31; // -'1'
_tatQuestCount = _tatHeaders[episode].questionsNum;
questOffset = _tatHeaders[episode].questionsOffset;
delete[] _tatQuestions;
_tatQuestions = new TlcTatQuestions[_tatQuestCount];
// Open tataidb.rle and seek correct position
tataidbfile = SearchMan.createReadStreamForMember("SYSTEM/TATAIDB.RLE");
if (!tataidbfile) {
error("TLC:TatLoadDB: Could not open 'SYSTEM/TATAIDB.RLE'");
}
tataidbfile->seek(questOffset, SEEK_SET);
for (int iQuest = 0; iQuest < _tatQuestCount; iQuest++) {
tataidbfile->read(_tatQuestions[iQuest].name, 5);
_tatQuestions[iQuest].name[5] = '\0';
_tatQuestions[iQuest].answerCount = tataidbfile->readByte();
for (int iAns = 0; iAns < _tatQuestions[iQuest].answerCount; iAns++) {
for (int iBin = 0; iBin < 16; iBin++) {
_tatQuestions[iQuest].answerData[iAns].binScore[iBin] = tataidbfile->readByte();
}
}
}
if (tataidbfile->eos()) {
error("TLC:TatLoadDB: Error reading questions from 'TATAIDB.RLE'");
}
// tatData[iQ*232 ] <= 5 Bytes Name
// tatData[iQ*232+6] <= 1 Byte iAnswers
}
void TlcGame::tatResultQuest() {
char questName[6];
int questIdx;
int selectedAns;
if (_tatQuestions == NULL) {
error("TLC:ResultQuest: Error, TATAIDB not loaded.");
}
// Get name of current question from script variables
for (int i = 0; i < 5; i++) {
questName[i] = _scriptVariables[0x41 + i] + 0x30;
}
questName[5] = '\0';
// search for question in the database
questIdx = -1;
do {
questIdx++;
if (questIdx >= _tatQuestCount) {
error("TLC:ResultQuest: Could not find question '%s' in TATAIDB. Count: %d", questName, _tatQuestCount);
}
} while (scumm_stricmp(questName, _tatQuestions[questIdx].name) != 0);
// Get selected answer. Range: 0..7
selectedAns = _scriptVariables[0x46];
if (selectedAns >= _tatQuestions[questIdx].answerCount) {
error("TLC:ResultQuest: Chosen answer out of range for question: '%s'. Answer: %d/%d, questIdx: %d", questName, selectedAns+1, _tatQuestions[questIdx].answerCount, questIdx);
}
// Add answer score for each bin to the dedicated script variables
for (int iBin = 0; iBin < 16; iBin++) {
int score = _tatQuestions[questIdx].answerData[selectedAns].binScore[iBin];
setScriptVar(0x4d + iBin, _scriptVariables[0x4D + iBin] + score);
}
}
void TlcGame::tatResultEpisode() {
int episode = _scriptVariables[0x47] - 0x31;
float ratioCur = 0;
float ratioA = 0;
float ratioB = 0;
int idxA = 0xff;
int idxB = 0xff;
int product;
char resultStrA[5];
char resultStrB[5];
// Process the bin scores
for (int iBin = 0; iBin < 16; iBin++) {
int binScoreSum = _scriptVariables[0x4D + iBin];
// increment 16 Bit interpreted variables with score sum of the current episode
setScriptVar16(0x5D + iBin * 2, getScriptVar16(0x5D + iBin * 2) + binScoreSum);
// Find the two biggest bin ratios. Remember idx and ratio, A is biggest, B second
if (binScoreSum != 0) {
ratioCur = (float)binScoreSum / (float)_tatHeaders[episode].binDividends[iBin];
if (ratioCur > ratioA) {
ratioB = ratioA;
idxB = idxA;
ratioA = ratioCur;
idxA = iBin;
} else {
if (ratioCur > ratioB) {
ratioB = ratioCur;
idxB = iBin;
}
}
}
}
// Process result according to biggest ratio (ratioA)
switch (idxA) {
case 0:
product = ratioA * 18.0;
if (product <= 3) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 4);
}
break;
case 1:
product = ratioA * 13.0;
if (product <= 3) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 4);
}
break;
case 2:
product = ratioA * 12.0;
if (product <= 3) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 4);
}
break;
case 3:
product = ratioA * 13.0;
if (product <= 8) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 9);
}
break;
case 4:
product = ratioA * 11.0;
if (product <= 3) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 4);
}
break;
case 5:
product = ratioA * 11.0;
if (product >= 4) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product - 4);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product);
}
break;
case 6:
product = ratioA * 9.0;
if (product <= 4) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 5);
}
break;
case 7:
product = ratioA * 10.0;
if (product <= 3) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 4);
}
break;
case 8:
product = ratioA * 12.0;
if (product <= 4) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product - 5);
}
break;
case 9:
product = ratioA * 10.0;
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product);
break;
case 10:
product = ratioA * 7.0;
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product);
break;
case 11:
product = ratioA * 10.0;
if (product >= 4) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product - 4);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product);
}
break;
case 12:
product = ratioA * 9.0;
if (product >= 4) {
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product - 4);
} else {
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product);
}
break;
case 13:
product = ratioA * 6.0;
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product);
break;
case 14:
product = ratioA * 7.0;
Common::sprintf_s(resultStrA, "%cP%02d", idxA + 'A', product);
break;
case 15:
product = ratioA * 8.0;
Common::sprintf_s(resultStrA, "%cN%02d", idxA + 'A', product);
break;
}
// The same for the second biggest ratio (ratioB)
switch (idxB) {
case 0:
product = ratioB * 18.0;
if (product <= 3) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 4);
}
break;
case 1:
product = ratioB * 13.0;
if (product <= 3) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 4);
}
break;
case 2:
product = ratioB * 12.0;
if (product <= 3) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 4);
}
break;
case 3:
product = ratioB * 13.0;
if (product <= 8) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 9);
}
break;
case 4:
product = ratioB * 11.0;
if (product <= 3) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 4);
}
break;
case 5:
product = ratioB * 11.0;
if (product >= 4) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product - 4);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product);
}
break;
case 6:
product = ratioB * 9.0;
if (product <= 4) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 5);
}
break;
case 7:
product = ratioB * 10.0;
if (product <= 3) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 4);
}
break;
case 8:
product = ratioB * 12.0;
if (product <= 4) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product - 5);
}
break;
case 9:
product = ratioB * 10.0;
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product);
break;
case 10:
product = ratioB * 7.0;
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product);
break;
case 11:
product = ratioB * 10.0;
if (product >= 4) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product - 4);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product);
}
break;
case 12:
product = ratioB * 9.0;
if (product >= 4) {
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product - 4);
} else {
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product);
}
break;
case 13:
product = ratioB * 6.0;
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product);
break;
case 14:
product = ratioB * 7.0;
Common::sprintf_s(resultStrB, "%cP%02d", idxB + 'A', product);
break;
case 15:
product = ratioB * 8.0;
Common::sprintf_s(resultStrB, "%cN%02d", idxB + 'A', product);
break;
}
// Write result to script variables
setScriptVar(0x4D, resultStrA[0] - 0x30);
setScriptVar(0x4E, resultStrA[1] - 0x30);
setScriptVar(0x4F, resultStrA[2] - 0x30);
setScriptVar(0x50, resultStrA[3] - 0x30);
setScriptVar(0x51, resultStrB[0] - 0x30);
setScriptVar(0x52, resultStrB[1] - 0x30);
setScriptVar(0x53, resultStrB[2] - 0x30);
setScriptVar(0x54, resultStrB[3] - 0x30);
setScriptVar(0x55, '\0');
}
void TlcGame::tatGetProfile() {
uint16 sumBinDivs[16];
float binRatios[16];
int iBin, iEpisode;
for (iBin = 0; iBin < 16; iBin++) {
sumBinDivs[iBin] = 0;
}
// Load scoretable by summing all dividends for each episode
tatLoadDBHeaders();
for (iEpisode = 0; iEpisode < _tatEpisodes; iEpisode++) {
for (iBin = 0; iBin < 16; iBin++) {
sumBinDivs[iBin] += _tatHeaders[iEpisode].binDividends[iBin];
}
}
// Calculate ratio of each bin
for (iBin = 0; iBin < 16; iBin++) {
binRatios[iBin] = (float)getScriptVar16(0x5D + 2 * iBin) / (float)sumBinDivs[iBin];
}
// Select higher ratio of each pair (A=iBin and B=iBin+1) and 1 or 2 according to threshold
for (iBin = 0; iBin < 16; iBin += 2) {
if (binRatios[iBin] > binRatios[iBin + 1]) {
setScriptVar(0x4d + iBin, 'A' + iBin);
if (binRatios[iBin] > 0.5) {
setScriptVar(0x4e + iBin, '1');
} else {
setScriptVar(0x4e + iBin, '2');
}
} else {
setScriptVar(0x4d + iBin, 'B' + iBin);
if (binRatios[iBin + 1] > 0.5) {
setScriptVar(0x4e + iBin, '1');
} else {
setScriptVar(0x4e + iBin, '2');
}
}
}
// Adapt former set script variables (all -0x30)
for (iBin = 0; iBin < 16; iBin++) {
setScriptVar(0x4d + iBin, _scriptVariables[0x4d + iBin] - '0');
}
}
} // End of Namespace Groovie