scummvm/engines/nancy/action/datarecords.cpp
2024-02-19 20:46:17 +01:00

467 lines
12 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/nancy/nancy.h"
#include "engines/nancy/util.h"
#include "engines/nancy/action/datarecords.h"
#include "engines/nancy/state/scene.h"
namespace Nancy {
namespace Action {
void TableIndexSetValueHS::readData(Common::SeekableReadStream &stream) {
_tableIndex = stream.readUint16LE();
_valueChangeType = stream.readByte();
_entryCorrectFlagID = stream.readSint16LE();
_allEntriesCorrectFlagID = stream.readSint16LE();
_flags.readData(stream);
_cursorType = stream.readUint16LE();
uint16 numHotspots = stream.readUint16LE();
_hotspots.resize(numHotspots);
for (uint i = 0; i < numHotspots; ++i) {
_hotspots[i].readData(stream);
}
}
void TableIndexSetValueHS::execute() {
switch (_state) {
case kBegin:
_state = kRun;
// fall through
case kRun:
_hasHotspot = false;
for (uint i = 0; i < _hotspots.size(); ++i) {
if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
_hasHotspot = true;
_hotspot = _hotspots[i].coords;
}
}
break;
case kActionTrigger: {
TableData *playerTable = (TableData *)NancySceneState.getPuzzleData(TableData::getTag());
assert(playerTable);
auto *tabl = GetEngineData(TABL);
assert(tabl);
// Edit table. Values start from 1!
switch (_valueChangeType) {
case kNoChangeTableValue:
break;
case kIncrementTableValue:
++playerTable->singleValues[_tableIndex - 1];
if (playerTable->singleValues[_tableIndex - 1] >= (int)playerTable->singleValues.size() + 1) {
playerTable->singleValues[_tableIndex - 1] = 1;
}
break;
case kDecrementTableValue:
--playerTable->singleValues[_tableIndex - 1];
if (playerTable->singleValues[_tableIndex - 1] == 0) {
playerTable->singleValues[_tableIndex - 1] = playerTable->singleValues.size();
}
break;
}
// Check for correctness...
// ...of current index only...
if (playerTable->singleValues[_tableIndex] == tabl->correctIDs[_tableIndex]) {
NancySceneState.setEventFlag(_entryCorrectFlagID, g_nancy->_true);
} else {
NancySceneState.setEventFlag(_entryCorrectFlagID, g_nancy->_false);
}
// ..and of all indices
bool allCorrect = true;
for (uint i = 0; i < tabl->correctIDs.size(); ++i) {
if (playerTable->singleValues[i] != tabl->correctIDs[i]) {
allCorrect = false;
break;
}
}
if (allCorrect) {
NancySceneState.setEventFlag(_allEntriesCorrectFlagID, g_nancy->_true);
} else {
NancySceneState.setEventFlag(_allEntriesCorrectFlagID, g_nancy->_false);
}
_flags.execute();
finishExecution();
}
}
}
void SetValue::readData(Common::SeekableReadStream &stream) {
_index = stream.readByte();
_shouldSet = stream.readByte();
_value = stream.readSint16LE();
}
void SetValue::execute() {
TableData *playerTable = (TableData *)NancySceneState.getPuzzleData(TableData::getTag());
assert(playerTable);
// nancy8 has 20 single & 20 combo values, later games have 30/10
uint numSingleValues = g_nancy->getGameType() <= kGameTypeNancy8 ? 20 : 30;
if (_index < numSingleValues) {
// Single values
int16 curValue = playerTable->getSingleValue(_index);
if (_shouldSet || curValue == kNoTableValue) {
playerTable->setSingleValue(_index, _value);
} else {
playerTable->setSingleValue(_index, curValue + _value);
}
} else {
// Combo values
float curValue = playerTable->getComboValue(_index - numSingleValues);
if (_shouldSet || curValue == (float)kNoTableValue) {
playerTable->setComboValue(_index - numSingleValues, _value);
} else {
playerTable->setComboValue(_index - numSingleValues, curValue + _value);
}
}
finishExecution();
}
void SetValueCombo::readData(Common::SeekableReadStream &stream) {
_valueIndex = stream.readByte();
_indices.resize(10);
_percentages.resize(10);
for (uint i = 0; i < 10; ++i) {
_indices[i] = stream.readByte();
_percentages[i] = stream.readSint16LE();
}
}
void SetValueCombo::execute() {
TableData *playerTable = (TableData *)NancySceneState.getPuzzleData(TableData::getTag());
assert(playerTable);
// nancy8 has 20 single & 20 combo values, later games have 30/10
uint numSingleValues = g_nancy->getGameType() <= kGameTypeNancy8 ? 20 : 30;
playerTable->setComboValue(_valueIndex - numSingleValues, 0);
for (uint i = 0; i < _indices.size(); ++i) {
if (_indices[i] != kNoTableIndex) {
float valueToAdd = 0;
if (_indices[i] == 100) { // ACTUAL_VALUE
valueToAdd = _percentages[i];
} else {
if (_indices[i] < numSingleValues) {
// Add a single value
if (playerTable->singleValues[_indices[i]] != kNoTableValue) {
valueToAdd = playerTable->singleValues[_indices[i]];
valueToAdd = valueToAdd * ((float)_percentages[i] / 100.f);
}
} else {
// Add another combo value
if (playerTable->comboValues[_indices[i] - numSingleValues] != kNoTableValue) {
valueToAdd = playerTable->comboValues[_indices[i] - numSingleValues];
valueToAdd = valueToAdd * ((float)_percentages[i] / 100.f);
}
}
}
playerTable->setComboValue(_valueIndex - numSingleValues, playerTable->getComboValue(_valueIndex - numSingleValues) + valueToAdd);
}
}
finishExecution();
}
void ValueTest::readData(Common::SeekableReadStream &stream) {
_valueIndex = stream.readByte();
_testType = stream.readByte();
_condition = stream.readByte();
_indicesToTest.resize(5);
for (uint i = 0; i < 5; ++i) {
_indicesToTest[i] = stream.readByte();
}
_flagToSet = stream.readSint16LE();
}
static const byte kTestAllCombo = 0;
static const byte kTestAllSingle = 1;
static const byte kTestSome = 2;
static const byte kTestActualValue = 3;
static const byte kTestEqualTo = 0;
static const byte kTestLessThan = 1;
static const byte kTestGreaterThan = 2;
static const byte kTestGreaterThanOrEqual = 3;
static const byte kTestLessThanOrEqual = 4;
void ValueTest::execute() {
TableData *playerTable = (TableData *)NancySceneState.getPuzzleData(TableData::getTag());
assert(playerTable);
// nancy8 has 20 single & 20 combo values, later games have 30/10
uint numSingleValues = g_nancy->getGameType() <= kGameTypeNancy8 ? 20 : 30;
float testedValue;
if (_valueIndex < numSingleValues) {
// Test a single value
testedValue = playerTable->getSingleValue(_valueIndex);
} else {
// Test a combo value
testedValue = playerTable->getComboValue(_valueIndex - numSingleValues);
}
// Pick which values we will test against, depending on the _testType param
Common::Array<byte> testedIndices;
switch (_testType) {
case kTestAllSingle:
testedIndices.resize(numSingleValues);
for (uint i = 0; i < numSingleValues; ++i) {
testedIndices[i] = i;
}
break;
case kTestAllCombo:
testedIndices.resize(g_nancy->getGameType() == kGameTypeNancy8 ? 20 : 10);
for (uint i = 0; i < testedIndices.size(); ++i) {
testedIndices[i] = i + numSingleValues;
}
break;
case kTestSome:
case kTestActualValue:
testedIndices = _indicesToTest;
break;
}
bool satisfied = false;
for (uint i = 0; i < testedIndices.size(); ++i) {
if (testedIndices[i] == kNoTableIndex) {
continue;
}
float otherValue = 0;
if (_testType == kTestActualValue) {
otherValue = testedIndices[i];
} else {
if (testedIndices[i] < numSingleValues) {
// Test against single value
otherValue = playerTable->getSingleValue(testedIndices[i]);
} else {
// Test against combo value
otherValue = playerTable->getComboValue(testedIndices[i] - numSingleValues);
}
if (otherValue == (float)kNoTableValue) {
continue;
}
}
switch (_condition) {
case kTestEqualTo:
if (testedValue == otherValue) {
satisfied = true;
}
break;
case kTestLessThan:
if (testedValue < otherValue) {
satisfied = true;
}
break;
case kTestGreaterThan:
if (testedValue > otherValue) {
satisfied = true;
}
break;
case kTestGreaterThanOrEqual:
if (testedValue >= otherValue) {
satisfied = true;
}
break;
case kTestLessThanOrEqual:
if (testedValue <= otherValue) {
satisfied = true;
}
break;
}
if (satisfied) {
break;
}
}
if (satisfied) {
NancySceneState.setEventFlag(_flagToSet, g_nancy->_true);
}
finishExecution();
}
void EventFlags::readData(Common::SeekableReadStream &stream) {
if (!_isTerse) {
_flags.readData(stream);
} else {
// Terse version only has 2 flags
_flags.descs[0].label = stream.readSint16LE();
_flags.descs[0].flag = stream.readUint16LE();
_flags.descs[1].label = stream.readSint16LE();
_flags.descs[1].flag = stream.readUint16LE();
}
}
void EventFlags::execute() {
_flags.execute();
_isDone = true;
}
void EventFlagsMultiHS::readData(Common::SeekableReadStream &stream) {
EventFlags::readData(stream);
if (_isCursor) {
_hoverCursor = (CursorManager::CursorType)stream.readUint16LE();
}
uint16 numHotspots = stream.readUint16LE();
_hotspots.reserve(numHotspots);
for (uint16 i = 0; i < numHotspots; ++i) {
_hotspots.push_back(HotspotDescription());
HotspotDescription &newDesc = _hotspots[i];
newDesc.readData(stream);
}
}
void EventFlagsMultiHS::execute() {
switch (_state) {
case kBegin:
// turn main rendering on
_state = kRun;
// fall through
case kRun:
_hasHotspot = false;
for (uint i = 0; i < _hotspots.size(); ++i) {
if (_hotspots[i].frameID == NancySceneState.getSceneInfo().frameID) {
_hasHotspot = true;
_hotspot = _hotspots[i].coords;
}
}
break;
case kActionTrigger:
if (_hoverCursor != CursorManager::kCustom1 && _hoverCursor != CursorManager::kCustom2) {
_hasHotspot = false;
EventFlags::execute();
finishExecution();
break;
} else {
_state = kRun;
}
}
}
void DifficultyLevel::readData(Common::SeekableReadStream &stream) {
_difficulty = stream.readUint16LE();
_flag.label = stream.readSint16LE();
_flag.flag = stream.readUint16LE();
}
void DifficultyLevel::execute() {
NancySceneState.setDifficulty(_difficulty);
NancySceneState.setEventFlag(_flag);
_isDone = true;
}
void ModifyListEntry::readData(Common::SeekableReadStream &stream) {
_surfaceID = stream.readUint16LE();
readFilename(stream, _stringID);
_mark = stream.readUint16LE();
if (g_nancy->getGameType() >= kGameTypeNancy9 && _mark >= 10) {
_sceneID = stream.readUint16LE();
}
}
void ModifyListEntry::execute() {
JournalData *journalData = (Nancy::JournalData *)NancySceneState.getPuzzleData(Nancy::JournalData::getTag());
assert(journalData);
Common::Array<JournalData::Entry> &array = journalData->journalEntries[_surfaceID];
JournalData::Entry *found = nullptr;
for (uint i = 0; i < array.size(); ++i) {
if (array[i].stringID == _stringID) {
found = &array[i];
break;
}
}
switch (_type) {
case kAdd:
if (!found) {
array.push_back(JournalData::Entry(_stringID, _mark, _sceneID));
}
break;
case kDelete:
if (found) {
array.erase(found);
}
break;
case kMark:
if (found) {
found->mark = _mark;
}
break;
}
finishExecution();
}
Common::String ModifyListEntry::getRecordTypeName() const {
switch (_type) {
case kAdd:
return "AddListEntry";
case kDelete:
return "DeleteListEntry";
case kMark:
return "MarkListEntry";
}
return "";
}
} // End of namespace Action
} // End of namespace Nancy