Initial commit
This commit is contained in:
3
engines/nancy/POTFILES
Normal file
3
engines/nancy/POTFILES
Normal file
@@ -0,0 +1,3 @@
|
||||
engines/nancy/input.cpp
|
||||
engines/nancy/metaengine.cpp
|
||||
engines/nancy/state/loadsave.cpp
|
||||
723
engines/nancy/action/actionmanager.cpp
Normal file
723
engines/nancy/action/actionmanager.cpp
Normal file
@@ -0,0 +1,723 @@
|
||||
/* 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 "common/serializer.h"
|
||||
#include "common/stack.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/random.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/font.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
|
||||
#include "engines/nancy/action/actionmanager.h"
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
#include "engines/nancy/action/secondarymovie.h"
|
||||
#include "engines/nancy/action/soundrecords.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
ActionManager::~ActionManager() {
|
||||
clearActionRecords();
|
||||
}
|
||||
|
||||
void ActionManager::handleInput(NancyInput &input) {
|
||||
bool setHoverCursor = false;
|
||||
for (auto &rec : _records) {
|
||||
if (rec->_isActive && !rec->_isDone) {
|
||||
// First, loop through all records and handle special cases.
|
||||
// This needs to be a separate loop to handle Overlays as a special case
|
||||
// (see note in Overlay::handleInput())
|
||||
rec->handleInput(input);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &rec : _records) {
|
||||
if ( rec->_isActive &&
|
||||
!rec->_isDone &&
|
||||
rec->_hasHotspot &&
|
||||
rec->_hotspot.isValidRect() && // Needed for nancy2 scene 1600
|
||||
NancySceneState.getViewport().convertViewportToScreen(rec->_hotspot).contains(input.mousePos)) {
|
||||
if (!setHoverCursor) {
|
||||
// Hotspots may overlap, but we want the hover cursor for the first one we encounter
|
||||
// This fixes the stairs in nancy3
|
||||
g_nancy->_cursor->setCursorType(rec->getHoverCursor());
|
||||
setHoverCursor = true;
|
||||
}
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
input.input &= ~NancyInput::kLeftMouseButtonUp;
|
||||
|
||||
rec->_cursorDependency = nullptr;
|
||||
processDependency(rec->_dependencies, *rec, false);
|
||||
|
||||
if (!rec->_dependencies.satisfied) {
|
||||
if (rec->_cursorDependency != nullptr) {
|
||||
NancySceneState.playItemCantSound(
|
||||
rec->_cursorDependency->label,
|
||||
(g_nancy->getGameType() <= kGameTypeNancy2 && rec->_cursorDependency->condition == kCursInvNotHolding));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
rec->_state = ActionRecord::ExecutionState::kActionTrigger;
|
||||
|
||||
input.eatMouseInput();
|
||||
|
||||
if (rec->_cursorDependency) {
|
||||
int16 item = rec->_cursorDependency->label;
|
||||
|
||||
// Re-add the object to the inventory unless it's marked as a one-time use
|
||||
if (item == NancySceneState.getHeldItem() && item != -1) {
|
||||
auto *inventoryData = GetEngineData(INV);
|
||||
assert(inventoryData);
|
||||
|
||||
switch (inventoryData->itemDescriptions[item].keepItem) {
|
||||
case kInvItemKeepAlways :
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy3) {
|
||||
// In nancy3 and up this means the object remains in hand, so do nothing
|
||||
// Older games had the kInvItemReturn behavior instead
|
||||
break;
|
||||
}
|
||||
|
||||
// fall through
|
||||
case kInvItemReturn :
|
||||
NancySceneState.addItemToInventory(item);
|
||||
// fall through
|
||||
case kInvItemUseThenLose :
|
||||
NancySceneState.setHeldItem(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rec->_cursorDependency = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionManager::addNewActionRecord(Common::SeekableReadStream &inputData) {
|
||||
ActionRecord *newRecord = createAndLoadNewRecord(inputData);
|
||||
if (!newRecord) {
|
||||
inputData.seek(0x30);
|
||||
byte ARType = inputData.readByte();
|
||||
|
||||
warning("Action Record type %i is unimplemented or invalid!", ARType);
|
||||
return;
|
||||
}
|
||||
_records.push_back(newRecord);
|
||||
}
|
||||
|
||||
ActionRecord *ActionManager::createAndLoadNewRecord(Common::SeekableReadStream &inputData) {
|
||||
inputData.seek(0);
|
||||
char descBuf[0x30];
|
||||
inputData.read(descBuf, 0x30);
|
||||
descBuf[0x2F] = '\0';
|
||||
byte ARType = inputData.readByte();
|
||||
byte execType = inputData.readByte();
|
||||
ActionRecord *newRecord = createActionRecord(ARType, &inputData);
|
||||
|
||||
if (!newRecord) {
|
||||
newRecord = new Unimplemented();
|
||||
}
|
||||
|
||||
newRecord->_description = descBuf;
|
||||
newRecord->_type = ARType;
|
||||
newRecord->_execType = (ActionRecord::ExecutionType)execType;
|
||||
|
||||
newRecord->readData(inputData);
|
||||
|
||||
// If the remaining data is less than the total data, there must be dependencies at the end of the chunk
|
||||
int64 dataRemaining = inputData.size() - inputData.pos();
|
||||
if (dataRemaining > 0 && newRecord->getRecordTypeName() != "Unimplemented") {
|
||||
// Each dependency is 12 (up to nancy2) or 16 (nancy3 and up) bytes long
|
||||
uint singleDepSize = g_nancy->getGameType() <= kGameTypeNancy2 ? 12 : 16;
|
||||
uint numDependencies = dataRemaining / singleDepSize;
|
||||
if (dataRemaining % singleDepSize) {
|
||||
warning("Action record type %u, %s has incorrect read size!\ndescription:\n%s",
|
||||
newRecord->_type,
|
||||
newRecord->getRecordTypeName().c_str(),
|
||||
newRecord->_description.c_str());
|
||||
|
||||
delete newRecord;
|
||||
|
||||
newRecord = new Unimplemented();
|
||||
newRecord->_description = descBuf;
|
||||
newRecord->_type = ARType;
|
||||
newRecord->_execType = (ActionRecord::ExecutionType)execType;
|
||||
}
|
||||
|
||||
if (numDependencies == 0) {
|
||||
newRecord->_dependencies.satisfied = true;
|
||||
}
|
||||
|
||||
Common::Stack<DependencyRecord *> depStack;
|
||||
depStack.push(&newRecord->_dependencies);
|
||||
|
||||
// Initialize the dependencies data
|
||||
for (uint16 i = 0; i < numDependencies; ++i) {
|
||||
depStack.top()->children.push_back(DependencyRecord());
|
||||
DependencyRecord &dep = depStack.top()->children.back();
|
||||
|
||||
if (singleDepSize == 12) {
|
||||
dep.type = (DependencyType)inputData.readByte();
|
||||
dep.label = inputData.readByte();
|
||||
dep.condition = inputData.readByte();
|
||||
dep.orFlag = inputData.readByte();
|
||||
} else if (singleDepSize == 16) {
|
||||
dep.type = (DependencyType)inputData.readUint16LE();
|
||||
dep.label = inputData.readUint16LE();
|
||||
dep.condition = inputData.readUint16LE();
|
||||
dep.orFlag = inputData.readUint16LE();
|
||||
}
|
||||
|
||||
dep.hours = inputData.readSint16LE();
|
||||
dep.minutes = inputData.readSint16LE();
|
||||
dep.seconds = inputData.readSint16LE();
|
||||
dep.milliseconds = inputData.readSint16LE();
|
||||
|
||||
switch (dep.type) {
|
||||
case DependencyType::kElapsedPlayerTime:
|
||||
dep.timeData = dep.hours * 3600000 + dep.minutes * 60000;
|
||||
|
||||
if (g_nancy->getGameType() < kGameTypeNancy3) {
|
||||
// Older titles only checked if the time is less than the one in the dependency
|
||||
dep.condition = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kSceneCount:
|
||||
break;
|
||||
case DependencyType::kOpenParenthesis:
|
||||
depStack.push(&dep);
|
||||
break;
|
||||
case DependencyType::kCloseParenthesis:
|
||||
depStack.top()->children.pop_back();
|
||||
depStack.pop();
|
||||
break;
|
||||
default:
|
||||
if (dep.hours != -1 || dep.minutes != -1 || dep.seconds != -1) {
|
||||
dep.timeData = ((dep.hours * 60 + dep.minutes) * 60 + dep.seconds) * 1000 + dep.milliseconds;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Set new record to active if it doesn't depend on anything
|
||||
newRecord->_isActive = true;
|
||||
}
|
||||
|
||||
return newRecord;
|
||||
}
|
||||
|
||||
void ActionManager::processActionRecords() {
|
||||
bool activeRecordsThisFrame = false;
|
||||
_activatedRecordsThisFrame.clear();
|
||||
|
||||
for (auto record : _records) {
|
||||
if (record->_isDone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process dependencies every call. We make sure to ignore cursor dependencies,
|
||||
// as they are only handled when calling from handleInput()
|
||||
processDependency(record->_dependencies, *record, record->canHaveHotspot());
|
||||
record->_isActive = record->_dependencies.satisfied;
|
||||
|
||||
if (record->_isActive) {
|
||||
if(record->_state == ActionRecord::kBegin) {
|
||||
_activatedRecordsThisFrame.push_back(record);
|
||||
}
|
||||
|
||||
record->execute();
|
||||
_recordsWereExecuted = true;
|
||||
activeRecordsThisFrame = true;
|
||||
}
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy4 && NancySceneState._state == State::Scene::kLoad) {
|
||||
// changeScene() must have been called, abort any further processing.
|
||||
// Both old and new behavior is needed (nancy3 intro narration, nancy4 garden gate)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!activeRecordsThisFrame) {
|
||||
// No active records were found for this frame.
|
||||
// This will lead to an infinite loop without
|
||||
// anything happening, so we reset the
|
||||
// _recordsWereExecuted flag, to fall back to
|
||||
// the kDefaultAR dependency. This is needed for
|
||||
// some scenes in Nancy 8, where SetVolume() is
|
||||
// called, but no other action records are active.
|
||||
_recordsWereExecuted = false;
|
||||
}
|
||||
|
||||
synchronizeMovieWithSound();
|
||||
debugDrawHotspots();
|
||||
}
|
||||
|
||||
void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &record, bool doNotCheckCursor) {
|
||||
if (dep.children.size()) {
|
||||
// Recursively process child dependencies
|
||||
for (uint i = 0; i < dep.children.size(); ++i) {
|
||||
processDependency(dep.children[i], record, doNotCheckCursor);
|
||||
}
|
||||
|
||||
// An orFlag marks that its corresponding dependency and the one after it
|
||||
// mutually satisfy each other; if one is satisfied, so is the other. The effect
|
||||
// can be chained indefinitely (for example, the chiming clock in nancy3)
|
||||
for (uint i = 0; i < dep.children.size(); ++i) {
|
||||
if (dep.children[i].orFlag) {
|
||||
// Found an orFlag, start going down the chain of dependencies with orFlags
|
||||
bool foundSatisfied = false;
|
||||
for (uint j = i; j < dep.children.size(); ++j) {
|
||||
if (dep.children[j].satisfied) {
|
||||
// A dependency has been satisfied
|
||||
foundSatisfied = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!dep.children[j].orFlag) {
|
||||
// orFlag chain ended, no satisfied dependencies
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundSatisfied) {
|
||||
for (; i < dep.children.size(); ++i) {
|
||||
dep.children[i].satisfied = true;
|
||||
if (!dep.children[i].orFlag) {
|
||||
// Last element of orFlag chain
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If all children are satisfied, so is the parent
|
||||
dep.satisfied = true;
|
||||
for (uint i = 0; i < dep.children.size(); ++i) {
|
||||
if (!dep.children[i].satisfied) {
|
||||
dep.satisfied = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (dep.type) {
|
||||
case DependencyType::kNone:
|
||||
dep.satisfied = true;
|
||||
break;
|
||||
case DependencyType::kInventory:
|
||||
if (dep.condition == g_nancy->_false) {
|
||||
// Item not in possession or held
|
||||
if (NancySceneState._flags.items[dep.label] == g_nancy->_false &&
|
||||
dep.label != NancySceneState._flags.heldItem) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
} else {
|
||||
if (NancySceneState._flags.items[dep.label] == g_nancy->_true ||
|
||||
dep.label == NancySceneState._flags.heldItem) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kEvent:
|
||||
if (NancySceneState.getEventFlag(dep.label, dep.condition)) {
|
||||
// nancy1 has code for some timer array that never gets used
|
||||
// and is discarded from nancy2 onward
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kLogic:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy2) {
|
||||
// First few games used 2 for false and 1 for true, but we store them the
|
||||
// other way around here. So, we need to check for inequality
|
||||
if (!NancySceneState.getLogicCondition(dep.label, dep.condition)) {
|
||||
// Wait for specified time before satisfying dependency condition
|
||||
Time elapsed = NancySceneState._timers.lastTotalTime - NancySceneState._flags.logicConditions[dep.label].timestamp;
|
||||
|
||||
if (elapsed >= dep.timeData) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
} else {
|
||||
dep.satisfied = NancySceneState.getLogicCondition(dep.label, dep.condition);
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kElapsedGameTime:
|
||||
if (NancySceneState._timers.lastTotalTime >= dep.timeData) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kElapsedSceneTime:
|
||||
if (NancySceneState._timers.sceneTime >= dep.timeData) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kElapsedPlayerTime: {
|
||||
// We're only interested in the hours and minutes
|
||||
Time playerTime = NancySceneState._timers.playerTime.getHours() * 3600000 +
|
||||
NancySceneState._timers.playerTime.getMinutes() * 60000;
|
||||
switch (dep.condition) {
|
||||
case 0:
|
||||
dep.satisfied = dep.timeData < playerTime;
|
||||
break;
|
||||
case 1:
|
||||
dep.satisfied = dep.timeData > playerTime;
|
||||
break;
|
||||
case 2:
|
||||
dep.satisfied = dep.timeData == playerTime;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DependencyType::kSceneCount: {
|
||||
// Check how many times a scene has been visited.
|
||||
// This dependency type keeps its data in the time variables
|
||||
// Note: nancy7 completely flipped the meaning of 1 and 2
|
||||
int count = NancySceneState._flags.sceneCounts.contains(dep.hours) ?
|
||||
NancySceneState._flags.sceneCounts[dep.hours] : 0;
|
||||
switch (dep.milliseconds) {
|
||||
case 1:
|
||||
if ( (dep.minutes < count && g_nancy->getGameType() <= kGameTypeNancy6) ||
|
||||
(dep.minutes > count && g_nancy->getGameType() >= kGameTypeNancy7)) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case 2:
|
||||
if ( (dep.minutes > count && g_nancy->getGameType() <= kGameTypeNancy6) ||
|
||||
(dep.minutes < count && g_nancy->getGameType() >= kGameTypeNancy7)) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case 3:
|
||||
if (dep.minutes == count) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DependencyType::kElapsedPlayerDay:
|
||||
if (record._days == -1) {
|
||||
record._days = NancySceneState._timers.playerTime.getDays();
|
||||
dep.satisfied = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (record._days < NancySceneState._timers.playerTime.getDays()) {
|
||||
record._days = NancySceneState._timers.playerTime.getDays();
|
||||
|
||||
// This is not used in nancy3 and up, so it's a safe assumption that we
|
||||
// do not need to check types recursively
|
||||
for (uint j = 0; j < record._dependencies.children.size(); ++j) {
|
||||
if (record._dependencies.children[j].type == DependencyType::kElapsedPlayerTime) {
|
||||
record._dependencies.children[j].satisfied = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kCursorType: {
|
||||
if (doNotCheckCursor) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
bool isSatisfied = false;
|
||||
int heldItem = NancySceneState.getHeldItem();
|
||||
if (heldItem == -1 && dep.label == kCursStandard) {
|
||||
isSatisfied = true;
|
||||
} else {
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy2 && dep.condition == kCursInvNotHolding) {
|
||||
// Activate if _not_ holding the specified item. Dropped in nancy3
|
||||
if (heldItem != dep.label) {
|
||||
isSatisfied = true;
|
||||
}
|
||||
} else {
|
||||
// Activate if holding the specified item.
|
||||
if (heldItem == dep.label) {
|
||||
isSatisfied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dep.satisfied = isSatisfied;
|
||||
|
||||
if (isSatisfied) {
|
||||
// A satisfied dependency must be moved into the _cursorDependency slot, to make sure
|
||||
// the remove from/re-add to inventory logic works correctly
|
||||
record._cursorDependency = &dep;
|
||||
} else {
|
||||
if (record._cursorDependency == nullptr) {
|
||||
// However, if the current dependency was not satisfied, we only move it into
|
||||
// the _cursorDependency slot if nothing else was there before. This ensures
|
||||
// the "can't" sound played is the first dependency's
|
||||
record._cursorDependency = &dep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case DependencyType::kPlayerTOD:
|
||||
if (dep.label == NancySceneState.getPlayerTOD()) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kTimerLessThanDependencyTime:
|
||||
if (NancySceneState._timers.timerTime <= dep.timeData) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kTimerGreaterThanDependencyTime:
|
||||
if (NancySceneState._timers.timerTime > dep.timeData) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kDifficultyLevel:
|
||||
if (dep.condition == NancySceneState._difficulty) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kClosedCaptioning:
|
||||
if (ConfMan.getBool("subtitles")) {
|
||||
if (dep.condition == 2) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
} else {
|
||||
if (dep.condition == 1) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kSound:
|
||||
if (g_nancy->_sound->isSoundPlaying(dep.label)) {
|
||||
dep.satisfied = dep.condition == 1;
|
||||
} else {
|
||||
dep.satisfied = dep.condition == 0;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kRandom:
|
||||
// Pick a random number and compare it with the value in condition
|
||||
// This is only executed once
|
||||
if (!dep.stopEvaluating) {
|
||||
if ((int)g_nancy->_randomSource->getRandomNumber(99) < dep.condition) {
|
||||
dep.satisfied = true;
|
||||
} else {
|
||||
dep.satisfied = false;
|
||||
}
|
||||
|
||||
dep.stopEvaluating = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case DependencyType::kDefaultAR:
|
||||
// Only execute if no other AR has executed yet
|
||||
if (_recordsWereExecuted) {
|
||||
dep.satisfied = false;
|
||||
} else {
|
||||
dep.satisfied = true;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
warning("Unimplemented Dependency type %i", (int)dep.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionManager::clearActionRecords() {
|
||||
for (auto &r : _records) {
|
||||
delete r;
|
||||
}
|
||||
_records.clear();
|
||||
_recordsWereExecuted = false;
|
||||
}
|
||||
|
||||
void ActionManager::onPause(bool pause) {
|
||||
for (auto &r : _records) {
|
||||
if (r->_isActive && !r->_isDone) {
|
||||
r->onPause(pause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionManager::synchronize(Common::Serializer &ser) {
|
||||
// When loading, the records should already have been initialized by scene
|
||||
for (auto &rec : _records) {
|
||||
ser.syncAsByte(rec->_isActive);
|
||||
ser.syncAsByte(rec->_isDone);
|
||||
|
||||
// Forcefully re-activate Autotext records, since we need to regenerate the surface
|
||||
if (ser.isLoading() && g_nancy->getGameType() >= kGameTypeNancy6 && rec->_type == 61) {
|
||||
rec->_isDone = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionManager::synchronizeMovieWithSound() {
|
||||
// Improvement:
|
||||
|
||||
// The original engine had really bad timing issues with AVF videos,
|
||||
// as it set the next frame time by adding the frame length to the current evaluation
|
||||
// time, instead of to the time the previous frame was drawn. As a result, all
|
||||
// movie (and SecondaryVideos) frames play about 12 ms slower than they should.
|
||||
// This results in some unfortunate issues in nancy4: if we do as the original
|
||||
// engine did and just make frames 12 ms slower, some dialogue scenes (like scene 1400)
|
||||
// are very visibly not in sync; also, the entire videocam sequence suffers from
|
||||
// visible stitches where the scene changes not at the time it was intended to.
|
||||
// On the other hand, if instead we don't add those 12ms, that same videocam
|
||||
// sequence has a really nasty sound cutoff in the middle of a character speaking.
|
||||
|
||||
// This function intends to fix this issue by subtly manipulating the playback rate
|
||||
// of the movie so its length ends up matching that of the sound; if the sound rate was
|
||||
// changed instead, we would get slightly off-pitch dialogue, which would be undesirable.
|
||||
|
||||
// The heuristic for catching these cases relies on the scene having a movie and a sound
|
||||
// record start at the same frame, and have a (valid) scene change to the same scene.
|
||||
PlaySecondaryMovie *movie = nullptr;
|
||||
PlaySound *sound = nullptr;
|
||||
|
||||
for (uint i = 0; i < _activatedRecordsThisFrame.size(); ++i) {
|
||||
byte type = _activatedRecordsThisFrame[i]->_type;
|
||||
// Rely on _type for cheaper type check
|
||||
if (type == 53) {
|
||||
movie = dynamic_cast<PlaySecondaryMovie *>(_activatedRecordsThisFrame[i]);
|
||||
} else if (type == 150 || type == 151 || type == 157) {
|
||||
sound = dynamic_cast<PlaySound *>(_activatedRecordsThisFrame[i]);
|
||||
}
|
||||
|
||||
if (movie && sound) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (movie && sound && movie->_sound.name != "NO SOUND") {
|
||||
// A movie and a sound both got activated this frame, check if their scene changes match
|
||||
if ( movie->_videoSceneChange == PlaySecondaryMovie::kMovieSceneChange &&
|
||||
movie->_sceneChange.sceneID == sound->_sceneChange.sceneID &&
|
||||
movie->_sceneChange.sceneID != kNoScene) {
|
||||
// They match, check how long the sound is...
|
||||
Audio::Timestamp length = g_nancy->_sound->getLength(sound->_sound);
|
||||
|
||||
if (length.msecs() != 0) {
|
||||
// ..and set the movie's playback speed to match
|
||||
movie->_decoder->setRate(Common::Rational(movie->_decoder->getDuration().msecs(), length.msecs()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionManager::debugDrawHotspots() {
|
||||
// Draws a rectangle around (non-puzzle) hotspots as well as the id
|
||||
// and type of the owning ActionRecord. Hardcoded to font 0 since that's
|
||||
// the smallest one available in the engine.
|
||||
RenderObject &obj = NancySceneState._hotspotDebug;
|
||||
if (ConfMan.getBool("debug_hotspots", Common::ConfigManager::kTransientDomain)) {
|
||||
const Font *font = g_nancy->_graphics->getFont(0);
|
||||
assert(font);
|
||||
uint16 yOffset = NancySceneState.getViewport().getCurVerticalScroll();
|
||||
obj.setVisible(true);
|
||||
obj._drawSurface.clear(obj._drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < _records.size(); ++i) {
|
||||
ActionRecord *rec = _records[i];
|
||||
if (rec->_isActive && !rec->_isDone && rec->_hasHotspot) {
|
||||
Common::Rect hotspot = rec->_hotspot;
|
||||
hotspot.translate(0, -yOffset);
|
||||
hotspot.clip(obj._drawSurface.getBounds());
|
||||
|
||||
if (!hotspot.isEmpty()) {
|
||||
font->drawString(&obj._drawSurface, Common::String::format("%u, %s", i, rec->getRecordTypeName().c_str()),
|
||||
hotspot.left, hotspot.bottom - font->getFontHeight() - 2, hotspot.width(), 0,
|
||||
Graphics::kTextAlignCenter, 0, true);
|
||||
obj._drawSurface.frameRect(hotspot, 0xFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (obj.isVisible()) {
|
||||
obj.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
90
engines/nancy/action/actionmanager.h
Normal file
90
engines/nancy/action/actionmanager.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ACTIONMANAGER_H
|
||||
#define NANCY_ACTION_ACTIONMANAGER_H
|
||||
|
||||
#include "common/array.h"
|
||||
|
||||
namespace Common {
|
||||
class Serializer;
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
class NancyEngine;
|
||||
class NancyConsole;
|
||||
struct NancyInput;
|
||||
|
||||
namespace State {
|
||||
class Scene;
|
||||
}
|
||||
|
||||
namespace Action {
|
||||
|
||||
class ActionRecord;
|
||||
struct DependencyRecord;
|
||||
|
||||
// The class that handles ActionRecords and their execution
|
||||
class ActionManager {
|
||||
friend class Nancy::State::Scene;
|
||||
friend class Nancy::NancyConsole;
|
||||
|
||||
public:
|
||||
static const byte kCursInvHolding = 0;
|
||||
static const byte kCursInvNotHolding = 1;
|
||||
static const byte kCursStandard = 254;
|
||||
|
||||
ActionManager() {}
|
||||
virtual ~ActionManager();
|
||||
|
||||
void handleInput(NancyInput &input);
|
||||
|
||||
void processActionRecords();
|
||||
void processDependency(DependencyRecord &dep, ActionRecord &record, bool doNotCheckCursor);
|
||||
|
||||
void addNewActionRecord(Common::SeekableReadStream &inputData);
|
||||
Common::Array<ActionRecord *> &getActionRecords() { return _records; }
|
||||
ActionRecord *getActionRecord(uint id) { if (id < _records.size()) return _records[id]; else return nullptr;}
|
||||
void clearActionRecords();
|
||||
|
||||
void onPause(bool pause);
|
||||
|
||||
void synchronize(Common::Serializer &serializer);
|
||||
|
||||
protected:
|
||||
static ActionRecord *createActionRecord(uint16 type, Common::SeekableReadStream *recordStream = nullptr);
|
||||
static ActionRecord *createAndLoadNewRecord(Common::SeekableReadStream &inputData);
|
||||
|
||||
void synchronizeMovieWithSound();
|
||||
|
||||
void debugDrawHotspots();
|
||||
|
||||
Common::Array<ActionRecord *> _records;
|
||||
bool _recordsWereExecuted = false; // Used for kDefaultAR dependency
|
||||
Common::Array<ActionRecord *> _activatedRecordsThisFrame;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ACTIONMANAGER_H
|
||||
63
engines/nancy/action/actionrecord.cpp
Normal file
63
engines/nancy/action/actionrecord.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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 "common/stack.h"
|
||||
#include "engines/nancy/detection.h"
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void DependencyRecord::reset() {
|
||||
satisfied = false;
|
||||
for (auto &child : children) {
|
||||
child.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ActionRecord::finishExecution() {
|
||||
switch (_execType) {
|
||||
case kOneShot:
|
||||
_isDone = true;
|
||||
_state = kBegin;
|
||||
break;
|
||||
case kRepeating: {
|
||||
_isDone = false;
|
||||
_isActive = false;
|
||||
_state = kBegin;
|
||||
|
||||
_dependencies.reset();
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
_state = kBegin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Unimplemented::execute() {
|
||||
debugC(Nancy::kDebugActionRecord, "Unimplemented or changed ActionRecord type %u", _type);
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
169
engines/nancy/action/actionrecord.h
Normal file
169
engines/nancy/action/actionrecord.h
Normal file
@@ -0,0 +1,169 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ACTIONRECORD_H
|
||||
#define NANCY_ACTION_ACTIONRECORD_H
|
||||
|
||||
#include "engines/nancy/time.h"
|
||||
#include "engines/nancy/cursor.h"
|
||||
#include "engines/nancy/commontypes.h"
|
||||
#include "engines/nancy/renderobject.h"
|
||||
|
||||
namespace Common {
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
class NancyEngine;
|
||||
class NancyConsole;
|
||||
struct NancyInput;
|
||||
|
||||
namespace Action {
|
||||
|
||||
enum struct DependencyType : int16 {
|
||||
kNone = 0,
|
||||
kInventory = 1,
|
||||
kEvent = 2,
|
||||
kLogic = 3,
|
||||
kElapsedGameTime = 4,
|
||||
kElapsedSceneTime = 5,
|
||||
kElapsedPlayerTime = 6,
|
||||
kSamsSight = 7, // Not implemented
|
||||
kSamsSound = 8, // Not implemented
|
||||
kSceneCount = 9,
|
||||
kElapsedPlayerDay = 10,
|
||||
kCursorType = 11,
|
||||
kPlayerTOD = 12,
|
||||
kTimerLessThanDependencyTime = 13,
|
||||
kTimerGreaterThanDependencyTime = 14,
|
||||
kDifficultyLevel = 15,
|
||||
kClosedCaptioning = 16,
|
||||
kSound = 17,
|
||||
kOpenParenthesis = 18,
|
||||
kCloseParenthesis = 19,
|
||||
kRandom = 20,
|
||||
kDefaultAR = 21
|
||||
};
|
||||
|
||||
// Describes a condition that needs to be fulfilled before the
|
||||
// action record can be executed
|
||||
struct DependencyRecord {
|
||||
DependencyType type = DependencyType::kNone;
|
||||
int16 label = -1;
|
||||
int16 condition = -1;
|
||||
bool orFlag = false;
|
||||
int16 hours = -1;
|
||||
int16 minutes = -1;
|
||||
int16 seconds = -1;
|
||||
int16 milliseconds = -1;
|
||||
|
||||
bool satisfied = false;
|
||||
Time timeData;
|
||||
|
||||
// Only used for kRandom
|
||||
bool stopEvaluating = false;
|
||||
|
||||
// Used to support the dependency tree structure in nancy3 and up
|
||||
// The only valid field in dependencies with children is the orFlag
|
||||
Common::Array<DependencyRecord> children;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// Describes a single action that will be performed on every update.
|
||||
// Supports conditional execution (via dependencies) and can have
|
||||
// clickable hotspots on screen.
|
||||
// Does _not_ support drawing to screen, records that need this functionality
|
||||
// will have to subclass RenderActionRecord.
|
||||
class ActionRecord {
|
||||
friend class ActionManager;
|
||||
friend class Nancy::NancyConsole;
|
||||
|
||||
public:
|
||||
enum ExecutionState { kBegin, kRun, kActionTrigger };
|
||||
enum ExecutionType { kOneShot = 1, kRepeating = 2 };
|
||||
ActionRecord() :
|
||||
_type(0),
|
||||
_execType(kOneShot),
|
||||
_isActive(false),
|
||||
_isDone(false),
|
||||
_hasHotspot(false),
|
||||
_state(ExecutionState::kBegin),
|
||||
_days(-1),
|
||||
_cursorDependency(nullptr) {}
|
||||
virtual ~ActionRecord() {}
|
||||
|
||||
virtual void readData(Common::SeekableReadStream &stream) = 0;
|
||||
virtual void execute() {}
|
||||
virtual void onPause(bool pause) {}
|
||||
|
||||
virtual CursorManager::CursorType getHoverCursor() const { return CursorManager::kHotspot; }
|
||||
virtual void handleInput(NancyInput &input) {}
|
||||
|
||||
protected:
|
||||
void finishExecution();
|
||||
virtual bool canHaveHotspot() const { return false; } // Used for handling kCursorType dependency
|
||||
|
||||
// Used for debugging
|
||||
virtual Common::String getRecordTypeName() const = 0;
|
||||
|
||||
public:
|
||||
Common::String _description;
|
||||
byte _type;
|
||||
ExecutionType _execType;
|
||||
// 0x32 data
|
||||
DependencyRecord _dependencies;
|
||||
// 0x3A numDependencies
|
||||
bool _isActive;
|
||||
// 0x3C satisfiedDependencies[]
|
||||
// 0x48 timers[]
|
||||
// 0x78 orFlags[]
|
||||
bool _isDone;
|
||||
bool _hasHotspot;
|
||||
Common::Rect _hotspot;
|
||||
ExecutionState _state;
|
||||
int16 _days;
|
||||
DependencyRecord *_cursorDependency;
|
||||
};
|
||||
|
||||
// Base class for visual ActionRecords
|
||||
class RenderActionRecord : public virtual ActionRecord, public RenderObject {
|
||||
public:
|
||||
RenderActionRecord(uint zOrder) : RenderObject(zOrder) {}
|
||||
virtual ~RenderActionRecord() {}
|
||||
|
||||
// This makes sure the AR is re-added to the render system
|
||||
// when returning from a different state (e.g. the Help screen)
|
||||
void onPause(bool pause) override { if (!pause) registerGraphics(); }
|
||||
};
|
||||
|
||||
// Dummy AR for classes that haven't been implemented/don't work in the current game version
|
||||
class Unimplemented : public ActionRecord {
|
||||
void execute() override;
|
||||
void readData(Common::SeekableReadStream &stream) override {}
|
||||
Common::String getRecordTypeName() const override { return "Unimplemented"; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ACTIONRECORD_H
|
||||
414
engines/nancy/action/arfactory.cpp
Normal file
414
engines/nancy/action/arfactory.cpp
Normal file
@@ -0,0 +1,414 @@
|
||||
/* 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/action/datarecords.h"
|
||||
#include "engines/nancy/action/inventoryrecords.h"
|
||||
#include "engines/nancy/action/navigationrecords.h"
|
||||
#include "engines/nancy/action/soundrecords.h"
|
||||
#include "engines/nancy/action/miscrecords.h"
|
||||
|
||||
#include "engines/nancy/action/autotext.h"
|
||||
#include "engines/nancy/action/conversation.h"
|
||||
#include "engines/nancy/action/interactivevideo.h"
|
||||
#include "engines/nancy/action/overlay.h"
|
||||
#include "engines/nancy/action/secondaryvideo.h"
|
||||
#include "engines/nancy/action/secondarymovie.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/angletosspuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/arcadepuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/assemblypuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/bballpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/bulpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/bombpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/collisionpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/cubepuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/cuttingpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/matchpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/hamradiopuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/leverpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/mazechasepuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/memorypuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/mouselightpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/multibuildpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/onebuildpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/orderingpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/overridelockpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/passwordpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/peepholepuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/quizpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/raycastpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/riddlepuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/rippedletterpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/rotatinglockpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/safedialpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/setplayerclock.h"
|
||||
#include "engines/nancy/action/puzzle/sliderpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/soundequalizerpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/soundmatchpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/spigotpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/tangrampuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/telephone.h"
|
||||
#include "engines/nancy/action/puzzle/towerpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/turningpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/twodialpuzzle.h"
|
||||
#include "engines/nancy/action/puzzle/whalesurvivorpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableReadStream *recordStream) {
|
||||
switch (type) {
|
||||
case 10:
|
||||
return new Hot1FrSceneChange(CursorManager::kHotspot);
|
||||
case 11:
|
||||
return new HotMultiframeSceneChange(CursorManager::kHotspot);
|
||||
case 12:
|
||||
return new SceneChange();
|
||||
case 13:
|
||||
return new HotMultiframeMultisceneChange();
|
||||
case 14:
|
||||
return new Hot1FrSceneChange(CursorManager::kExit);
|
||||
case 15:
|
||||
return new Hot1FrSceneChange(CursorManager::kMoveForward);
|
||||
case 16:
|
||||
return new Hot1FrSceneChange(CursorManager::kMoveBackward);
|
||||
case 17:
|
||||
return new Hot1FrSceneChange(CursorManager::kMoveUp);
|
||||
case 18:
|
||||
return new Hot1FrSceneChange(CursorManager::kMoveDown);
|
||||
case 19:
|
||||
return new HotMultiframeSceneChange(CursorManager::kMoveForward);
|
||||
case 20:
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
return new PaletteThisScene();
|
||||
} else {
|
||||
return new HotMultiframeSceneChange(CursorManager::kMoveUp);
|
||||
}
|
||||
case 21:
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
return new PaletteNextScene();
|
||||
} else {
|
||||
return new HotMultiframeSceneChange(CursorManager::kMoveDown);
|
||||
}
|
||||
case 22:
|
||||
return new Hot1FrSceneChange(CursorManager::kMoveLeft);
|
||||
case 23:
|
||||
return new Hot1FrSceneChange(CursorManager::kMoveRight);
|
||||
case 24:
|
||||
return new HotMultiframeMultisceneCursorTypeSceneChange();
|
||||
case 25: {
|
||||
// Weird case; instead of storing the cursor id, they instead chose to store
|
||||
// an AR id corresponding to one of the directional Hot1FrSceneChange variants.
|
||||
// Thus, we need to scan the incoming chunk and make another call to createActionRecord().
|
||||
// This is not the most elegant solution, but it works :)
|
||||
assert(recordStream);
|
||||
uint16 innerID = recordStream->readUint16LE();
|
||||
Hot1FrSceneChange *newRec = dynamic_cast<Hot1FrSceneChange *>(createActionRecord(innerID));
|
||||
assert(newRec);
|
||||
newRec->_isTerse = true;
|
||||
return newRec;
|
||||
}
|
||||
case 26:
|
||||
return new InteractiveVideo();
|
||||
case 40:
|
||||
if (g_nancy->getGameType() < kGameTypeNancy2) {
|
||||
// Only used in TVD
|
||||
return new LightningOn();
|
||||
} else {
|
||||
return new SpecialEffect();
|
||||
}
|
||||
case 50:
|
||||
return new ConversationVideo(); // PlayPrimaryVideoChan0
|
||||
case 51:
|
||||
case 52:
|
||||
return new PlaySecondaryVideo();
|
||||
case 53:
|
||||
return new PlaySecondaryMovie();
|
||||
case 54:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy1) {
|
||||
return new Overlay(false); // PlayStaticBitmapAnimation
|
||||
} else {
|
||||
return new Overlay(true);
|
||||
}
|
||||
case 55:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy1) {
|
||||
return new Overlay(true); // PlayIntStaticBitmapAnimation
|
||||
} else if (g_nancy->getGameType() >= kGameTypeNancy7) {
|
||||
return new OverlayStaticTerse();
|
||||
}
|
||||
return nullptr;
|
||||
case 56:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy6) {
|
||||
return new ConversationVideo();
|
||||
} else {
|
||||
return new OverlayAnimTerse();
|
||||
}
|
||||
return nullptr;
|
||||
case 57:
|
||||
return new ConversationCel();
|
||||
case 58:
|
||||
return new ConversationSound();
|
||||
case 59:
|
||||
return new ConversationCelT();
|
||||
case 60:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy5) {
|
||||
// Only used in tvd and nancy1
|
||||
return new MapCall();
|
||||
} else {
|
||||
return new ConversationSoundT();
|
||||
}
|
||||
case 61:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy5) {
|
||||
// Only used in tvd and nancy1
|
||||
return new MapCallHot1Fr();
|
||||
} else {
|
||||
return new Autotext();
|
||||
}
|
||||
case 62:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy7) {
|
||||
return new MapCallHotMultiframe(); // TVD/nancy1 only
|
||||
} else {
|
||||
return new ConversationCelTerse(); // nancy8 and up
|
||||
}
|
||||
case 63:
|
||||
return new ConversationSoundTerse();
|
||||
case 65:
|
||||
return new TableIndexOverlay();
|
||||
case 66:
|
||||
return new TableIndexPlaySound();
|
||||
case 67:
|
||||
return new TableIndexSetValueHS();
|
||||
case 68:
|
||||
return new TextScroll(false);
|
||||
case 70:
|
||||
return new TextScroll(true); // AutotextEntryList
|
||||
case 71:
|
||||
return new ModifyListEntry(ModifyListEntry::kAdd);
|
||||
case 72:
|
||||
return new ModifyListEntry(ModifyListEntry::kDelete);
|
||||
case 73:
|
||||
return new ModifyListEntry(ModifyListEntry::kMark);
|
||||
case 75:
|
||||
return new TextBoxWrite();
|
||||
case 76:
|
||||
return new TextboxClear();
|
||||
case 77:
|
||||
return new SetValue();
|
||||
case 78:
|
||||
return new SetValueCombo();
|
||||
case 79:
|
||||
return new ValueTest();
|
||||
case 97:
|
||||
return new EventFlags(true);
|
||||
case 98:
|
||||
return new EventFlagsMultiHS(true, true);
|
||||
case 99:
|
||||
return new EventFlagsMultiHS(true);
|
||||
case 100:
|
||||
return new BumpPlayerClock();
|
||||
case 101:
|
||||
return new SaveContinueGame();
|
||||
case 102:
|
||||
return new TurnOffMainRendering();
|
||||
case 103:
|
||||
return new TurnOnMainRendering();
|
||||
case 104:
|
||||
return new ResetAndStartTimer();
|
||||
case 105:
|
||||
return new StopTimer();
|
||||
case 106:
|
||||
return new EventFlagsMultiHS(false);
|
||||
case 107:
|
||||
return new EventFlags();
|
||||
case 108:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy6) {
|
||||
return new OrderingPuzzle(OrderingPuzzle::kOrdering);
|
||||
} else {
|
||||
return new GotoMenu();
|
||||
}
|
||||
case 109:
|
||||
return new LoseGame();
|
||||
case 110:
|
||||
return new PushScene();
|
||||
case 111:
|
||||
return new PopScene();
|
||||
case 112:
|
||||
return new WinGame();
|
||||
case 113:
|
||||
return new DifficultyLevel();
|
||||
case 114:
|
||||
return new RotatingLockPuzzle();
|
||||
case 115:
|
||||
return new LeverPuzzle();
|
||||
case 116:
|
||||
return new Telephone(false);
|
||||
case 117:
|
||||
return new SliderPuzzle();
|
||||
case 118:
|
||||
return new PasswordPuzzle();
|
||||
case 119:
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy7) {
|
||||
// This got moved in nancy7
|
||||
return new OrderingPuzzle(OrderingPuzzle::kOrdering);
|
||||
}
|
||||
return nullptr;
|
||||
case 120:
|
||||
return new AddInventoryNoHS();
|
||||
case 121:
|
||||
return new RemoveInventoryNoHS();
|
||||
case 122:
|
||||
return new ShowInventoryItem();
|
||||
case 123:
|
||||
return new InventorySoundOverride();
|
||||
case 124:
|
||||
return new EnableDisableInventory();
|
||||
case 125:
|
||||
return new PopInvViewPriorScene();
|
||||
case 126:
|
||||
return new GoInvViewScene();
|
||||
case 140:
|
||||
return new SetVolume();
|
||||
case 148:
|
||||
// MakeScreenFile - seems to save a cropped image of the screen in a bitmap file?
|
||||
// TODO: Used in Nancy 9, sand castle puzzle
|
||||
return nullptr;
|
||||
case 149:
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
||||
// This got moved in nancy9
|
||||
return new SetVolume();
|
||||
}
|
||||
return nullptr;
|
||||
case 150:
|
||||
return new PlaySound();
|
||||
case 151:
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy6) {
|
||||
return new PlaySound(); // PlayStreamSound
|
||||
} else {
|
||||
return new PlayRandomSoundTerse();
|
||||
}
|
||||
case 152:
|
||||
return new PlaySoundFrameAnchor();
|
||||
case 153:
|
||||
return new PlaySoundMultiHS();
|
||||
case 154:
|
||||
return new StopSound();
|
||||
case 155:
|
||||
return new StopSound(); // StopAndUnloadSound, but we always unload
|
||||
case 157:
|
||||
return new PlaySoundCC();
|
||||
case 158:
|
||||
return new PlayRandomSound();
|
||||
case 159:
|
||||
return new PlaySoundTerse();
|
||||
case 160:
|
||||
return new HintSystem();
|
||||
case 161:
|
||||
return new PlaySoundEventFlagTerse();
|
||||
case 170:
|
||||
return new SetPlayerClock();
|
||||
case 200:
|
||||
return new SoundEqualizerPuzzle();
|
||||
case 201:
|
||||
return new TowerPuzzle();
|
||||
case 202:
|
||||
return new BombPuzzle();
|
||||
case 203:
|
||||
return new RippedLetterPuzzle();
|
||||
case 204:
|
||||
return new OverrideLockPuzzle();
|
||||
case 205:
|
||||
return new RiddlePuzzle();
|
||||
case 206:
|
||||
return new RaycastPuzzle();
|
||||
case 207:
|
||||
return new TangramPuzzle();
|
||||
case 208:
|
||||
return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kPiano);
|
||||
case 209:
|
||||
return new TurningPuzzle();
|
||||
case 210:
|
||||
return new SafeDialPuzzle();
|
||||
case 211:
|
||||
return new CollisionPuzzle(CollisionPuzzle::PuzzleType::kCollision);
|
||||
case 212:
|
||||
return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kOrderItems);
|
||||
case 213:
|
||||
return new CollisionPuzzle(CollisionPuzzle::PuzzleType::kTileMove);
|
||||
case 214:
|
||||
return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kKeypad);
|
||||
case 215:
|
||||
return new MazeChasePuzzle();
|
||||
case 216:
|
||||
return new PeepholePuzzle();
|
||||
case 217:
|
||||
return new MouseLightPuzzle();
|
||||
case 218:
|
||||
return new BulPuzzle();
|
||||
case 219:
|
||||
return new BBallPuzzle();
|
||||
case 220:
|
||||
return new TwoDialPuzzle();
|
||||
case 221:
|
||||
return new HamRadioPuzzle();
|
||||
case 222:
|
||||
return new AssemblyPuzzle();
|
||||
case 223:
|
||||
return new CubePuzzle();
|
||||
case 224:
|
||||
return new OrderingPuzzle(OrderingPuzzle::kKeypadTerse);
|
||||
case 225:
|
||||
return new SpigotPuzzle();
|
||||
// -- Nancy 8 and up --
|
||||
case 226:
|
||||
return new CuttingPuzzle();
|
||||
case 228:
|
||||
return new MatchPuzzle();
|
||||
case 229:
|
||||
return new ArcadePuzzle();
|
||||
case 230:
|
||||
return new Telephone(true);
|
||||
case 231:
|
||||
return new QuizPuzzle();
|
||||
case 232:
|
||||
return new AngleTossPuzzle();
|
||||
// -- Nancy 9 and up --
|
||||
case 233:
|
||||
return new SoundMatchPuzzle();
|
||||
case 234:
|
||||
return new OneBuildPuzzle();
|
||||
case 235:
|
||||
return new MultiBuildPuzzle();
|
||||
case 237:
|
||||
return new WhaleSurvivorPuzzle();
|
||||
case 238:
|
||||
return new MemoryPuzzle();
|
||||
default:
|
||||
warning("Unknown action record type %d", type);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
205
engines/nancy/action/autotext.cpp
Normal file
205
engines/nancy/action/autotext.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
/* 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/action/autotext.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void Autotext::readData(Common::SeekableReadStream &stream) {
|
||||
_transparency = stream.readUint16LE();
|
||||
_surfaceID = stream.readUint16LE();
|
||||
_fontID = stream.readUint16LE();
|
||||
_defaultTextColor = stream.readUint16LE();
|
||||
_offset.x = stream.readUint16LE();
|
||||
_offset.y = stream.readUint16LE();
|
||||
_surfWidth = stream.readUint16LE();
|
||||
_surfHeight = stream.readUint16LE();
|
||||
|
||||
Common::Path imageName;
|
||||
readFilename(stream, imageName);
|
||||
|
||||
uint16 numImages = stream.readUint16LE();
|
||||
if (numImages) {
|
||||
for (uint i = 0; i < numImages; ++i) {
|
||||
uint16 line = stream.readUint16LE();
|
||||
Common::Rect src;
|
||||
readRect(stream, src);
|
||||
addImage(line, src);
|
||||
}
|
||||
|
||||
setImageName(imageName);
|
||||
}
|
||||
stream.skip((5 - numImages) * (2 + 16));
|
||||
|
||||
readExtraData(stream);
|
||||
}
|
||||
|
||||
void Autotext::readExtraData(Common::SeekableReadStream &stream) {
|
||||
_useAutotextChunk = stream.readByte();
|
||||
readFilename(stream, _textKey);
|
||||
|
||||
uint sizeText = stream.readUint16LE();
|
||||
|
||||
if (sizeText) {
|
||||
char *buf = new char[sizeText];
|
||||
stream.read(buf, sizeText);
|
||||
assembleTextLine(buf, _embeddedText, sizeText);
|
||||
delete[] buf;
|
||||
}
|
||||
}
|
||||
|
||||
void Autotext::execute() {
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
|
||||
if (_surfaceID > 2) {
|
||||
// Surfaces 3+ are journal surfaces, and their text contents are saved. Texts MUST be in CONVO chunk,
|
||||
// so we do not check _useAutotextChunk
|
||||
Nancy::JournalData *journalData = (Nancy::JournalData *)NancySceneState.getPuzzleData(Nancy::JournalData::getTag());
|
||||
assert(journalData);
|
||||
const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
|
||||
assert(autotext);
|
||||
|
||||
if (g_nancy->getGameType() == kGameTypeNancy7) {
|
||||
// In nancy7 ONLY, ids 6, 7 & 8 mean LIFO ordering for a suface
|
||||
if (_surfaceID > 5) {
|
||||
_order = kListLIFO;
|
||||
_surfaceID -= 3;
|
||||
}
|
||||
}
|
||||
|
||||
Common::String stringToPush;
|
||||
auto &entriesForSurface = journalData->journalEntries[_surfaceID];
|
||||
bool foundThisKey = false;
|
||||
for (auto &entry : entriesForSurface) {
|
||||
Common::String &stringID = entry.stringID;
|
||||
Common::String withTags = autotext->texts[stringID];
|
||||
|
||||
if (entry.mark % 10) {
|
||||
withTags = Common::String::format("<%u>", entry.mark % 10) + withTags;
|
||||
}
|
||||
|
||||
bool hasHotspot = false;
|
||||
|
||||
if (entry.mark >= 10 && entry.sceneID != kNoScene) {
|
||||
// The original engine uses tags <H#> and <L>
|
||||
withTags = "<H>" + withTags + "<L>";
|
||||
hasHotspot = true;
|
||||
}
|
||||
|
||||
if (_order == kListFIFO) {
|
||||
// Add to end of string
|
||||
stringToPush += withTags;
|
||||
|
||||
if (hasHotspot) {
|
||||
_hotspotScenes.push_back(entry.sceneID);
|
||||
}
|
||||
} else {
|
||||
// Add to front of string
|
||||
stringToPush = withTags + stringToPush;
|
||||
|
||||
if (hasHotspot) {
|
||||
_hotspotScenes.insert_at(0, entry.sceneID);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_textKey.empty() && stringID == _textKey) {
|
||||
foundThisKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
// A text key may not be present in the data (see AutotextEntryList), so do not attempt to add to the list then
|
||||
if (!foundThisKey && !_textKey.empty()) {
|
||||
// Key inside this Autotext instance wasn't found inside existing list, push it back and add it to string to draw
|
||||
if (_order == kListFIFO) {
|
||||
// Add to end of string
|
||||
stringToPush += autotext->texts[_textKey];
|
||||
} else {
|
||||
// Add to front of string
|
||||
stringToPush = autotext->texts[_textKey] + stringToPush;
|
||||
}
|
||||
|
||||
// Entry is always added to end; on-screen ordering happens when reading list
|
||||
entriesForSurface.push_back(JournalData::Entry(_textKey));
|
||||
}
|
||||
|
||||
addTextLine(stringToPush);
|
||||
} else {
|
||||
// Surfaces 0-2 have their contents cleared every scene (though we only bother doing so when we need to reuse)
|
||||
if (_useAutotextChunk) {
|
||||
// We have a key into the AUTOTEXT chunk
|
||||
const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
|
||||
assert(autotext);
|
||||
|
||||
addTextLine(autotext->texts[_textKey]);
|
||||
} else {
|
||||
// We have text embedded inside this Autotext instance
|
||||
addTextLine(_embeddedText);
|
||||
}
|
||||
}
|
||||
|
||||
// A height of zero means the surface doesn't need to be drawn
|
||||
if (_surfHeight) {
|
||||
// Guesstimate the height of the surface
|
||||
uint surfHeight = _textLines[0].size() / 144 * _surfWidth;
|
||||
surfHeight = MAX<uint>(surfHeight, _surfHeight + 20);
|
||||
Graphics::ManagedSurface &surf = g_nancy->_graphics->getAutotextSurface(_surfaceID);
|
||||
Common::Rect &surfBounds = g_nancy->_graphics->getAutotextSurfaceBounds(_surfaceID);
|
||||
surf.create(_surfWidth + 1, surfHeight, g_nancy->_graphics->getInputPixelFormat());
|
||||
if (_transparency) {
|
||||
surf.clear(g_nancy->_graphics->getTransColor());
|
||||
}
|
||||
|
||||
_fullSurface.create(surf, surf.getBounds());
|
||||
if(_transparency == kPlayOverlayTransparent) {
|
||||
_fullSurface.setTransparentColor(g_nancy->_graphics->getTransColor());
|
||||
}
|
||||
|
||||
Common::Rect textBounds = surf.getBounds();
|
||||
textBounds.left += _offset.x;
|
||||
textBounds.top += _offset.y;
|
||||
|
||||
const Font *font = g_nancy->_graphics->getFont(_fontID);
|
||||
assert(font);
|
||||
uint d = (font->getFontHeight() + 1) / 2 + 1;
|
||||
|
||||
textBounds.top += d + 1;
|
||||
textBounds.left += d;
|
||||
|
||||
// Original engine uses a particular value in the TBOX chunk in all
|
||||
// text rendering, including Autotext
|
||||
auto *tbox = GetEngineData(TBOX);
|
||||
|
||||
drawAllText(textBounds, tbox->leftOffset - textBounds.left, _fontID, _fontID);
|
||||
surfBounds = Common::Rect(_fullSurface.w, _drawnTextHeight);
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
74
engines/nancy/action/autotext.h
Normal file
74
engines/nancy/action/autotext.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_AUTOTEXT_H
|
||||
#define NANCY_ACTION_AUTOTEXT_H
|
||||
|
||||
#include "engines/nancy/misc/hypertext.h"
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Action record used for rendering text inside the game viewport.
|
||||
// Can be used in two ways: for single-use texts that get thrown away
|
||||
// after a scene change, or for permanent storage (used in nancy's journal)
|
||||
// Does not own or display any image data; it draws to a surface inside
|
||||
// GraphicsManager, which other ActionRecords (Overlay and PeepholePuzzle) can use.
|
||||
class Autotext : public virtual ActionRecord, public Misc::HypertextParser {
|
||||
public:
|
||||
Autotext() {}
|
||||
virtual ~Autotext() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "Autotext"; }
|
||||
|
||||
virtual void readExtraData(Common::SeekableReadStream &stream);
|
||||
|
||||
uint16 _transparency = kPlayOverlayPlain;
|
||||
uint16 _surfaceID = 0;
|
||||
uint16 _fontID = 0;
|
||||
uint16 _textColor = 0;
|
||||
Common::Point _offset;
|
||||
uint16 _surfWidth = 0;
|
||||
uint16 _surfHeight = 0;
|
||||
|
||||
bool _useAutotextChunk = false;
|
||||
|
||||
// Only one of these can be valid
|
||||
Common::String _textKey;
|
||||
Common::String _embeddedText;
|
||||
|
||||
uint16 _order = kListFIFO;
|
||||
bool _shouldDrawMarks = false;
|
||||
|
||||
Common::Array<uint16> _hotspotScenes;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_AUTOTEXT_H
|
||||
897
engines/nancy/action/conversation.cpp
Normal file
897
engines/nancy/action/conversation.cpp
Normal file
@@ -0,0 +1,897 @@
|
||||
/* 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 "common/random.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/serializer.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
|
||||
#include "engines/nancy/action/conversation.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
ConversationSound::ConversationSound() :
|
||||
RenderActionRecord(8),
|
||||
_noResponse(g_nancy->getGameType() <= kGameTypeNancy2 ? 10 : 20),
|
||||
_hasDrawnTextbox(false),
|
||||
_pickedResponse(-1) {
|
||||
_conditionalResponseCharacterID = _noResponse;
|
||||
_goodbyeResponseCharacterID = _noResponse;
|
||||
}
|
||||
|
||||
ConversationSound::~ConversationSound() {
|
||||
if (NancySceneState.getActiveConversation() == this) {
|
||||
NancySceneState.setActiveConversation(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationSound::init() {
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void ConversationSound::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Serializer ser(&stream, nullptr);
|
||||
ser.setVersion(g_nancy->getGameType());
|
||||
|
||||
if (ser.getVersion() >= kGameTypeNancy2) {
|
||||
_sound.readNormal(stream);
|
||||
}
|
||||
|
||||
readCaptionText(stream);
|
||||
|
||||
if (ser.getVersion() <= kGameTypeNancy1) {
|
||||
_sound.readNormal(stream);
|
||||
}
|
||||
|
||||
_responseGenericSound.readNormal(stream);
|
||||
ser.skip(1); // RESPONSE_STARTUP_CLEAR_ALL, RESPONSE_STARTUP_KEEP_OLD; never used (tested up to nancy5)
|
||||
ser.syncAsByte(_conditionalResponseCharacterID);
|
||||
ser.syncAsByte(_goodbyeResponseCharacterID);
|
||||
ser.syncAsByte(_defaultNextScene);
|
||||
ser.syncAsByte(_popNextScene);
|
||||
_sceneChange.readData(stream, ser.getVersion() == kGameTypeVampire);
|
||||
ser.skip(0x32, kGameTypeVampire, kGameTypeNancy1);
|
||||
ser.skip(2, kGameTypeNancy2);
|
||||
|
||||
uint16 numResponses = 0;
|
||||
ser.syncAsUint16LE(numResponses);
|
||||
|
||||
_responses.resize(numResponses);
|
||||
for (uint i = 0; i < numResponses; ++i) {
|
||||
ResponseStruct &response = _responses[i];
|
||||
response.conditionFlags.read(stream);
|
||||
readResponseText(stream, response);
|
||||
readFilename(stream, response.soundName);
|
||||
ser.syncAsByte(response.addRule);
|
||||
response.sceneChange.readData(stream, ser.getVersion() == kGameTypeVampire);
|
||||
ser.syncAsSint16LE(response.flagDesc.label);
|
||||
ser.syncAsByte(response.flagDesc.flag);
|
||||
ser.skip(0x32, kGameTypeVampire, kGameTypeNancy1);
|
||||
ser.skip(2, kGameTypeNancy2);
|
||||
}
|
||||
|
||||
uint16 numSceneBranchStructs = stream.readUint16LE();
|
||||
_sceneBranchStructs.resize(numSceneBranchStructs);
|
||||
for (uint i = 0; i < numSceneBranchStructs; ++i) {
|
||||
_sceneBranchStructs[i].conditions.read(stream);
|
||||
_sceneBranchStructs[i].sceneChange.readData(stream, g_nancy->getGameType() == kGameTypeVampire);
|
||||
ser.skip(0x32, kGameTypeVampire, kGameTypeNancy1);
|
||||
ser.skip(2, kGameTypeNancy2);
|
||||
}
|
||||
|
||||
uint16 numFlagsStructs = stream.readUint16LE();
|
||||
_flagsStructs.resize(numFlagsStructs);
|
||||
for (uint i = 0; i < numFlagsStructs; ++i) {
|
||||
FlagsStruct &flagsStruct = _flagsStructs[i];
|
||||
flagsStruct.conditions.read(stream);
|
||||
flagsStruct.flagToSet.type = stream.readByte();
|
||||
flagsStruct.flagToSet.flag.label = stream.readSint16LE();
|
||||
flagsStruct.flagToSet.flag.flag = stream.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationSound::readTerseData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _sound.name);
|
||||
_sound.volume = stream.readUint16LE();
|
||||
_sound.channelID = 12; // hardcoded
|
||||
_sound.numLoops = 1;
|
||||
|
||||
_responseGenericSound.volume = _sound.volume;
|
||||
_responseGenericSound.numLoops = 1;
|
||||
_responseGenericSound.channelID = 13; // hardcoded
|
||||
|
||||
readTerseCaptionText(stream);
|
||||
|
||||
_conditionalResponseCharacterID = stream.readByte();
|
||||
_goodbyeResponseCharacterID = stream.readByte();
|
||||
|
||||
_defaultNextScene = stream.readByte();
|
||||
|
||||
_sceneChange.sceneID = stream.readUint16LE();
|
||||
_sceneChange.continueSceneSound = kContinueSceneSound;
|
||||
|
||||
uint16 numResponses = stream.readUint16LE();
|
||||
_responses.resize(numResponses);
|
||||
for (uint i = 0; i < numResponses; ++i) {
|
||||
ResponseStruct &response = _responses[i];
|
||||
response.conditionFlags.read(stream);
|
||||
readTerseResponseText(stream, response);
|
||||
readFilename(stream, response.soundName);
|
||||
response.sceneChange.sceneID = stream.readUint16LE();
|
||||
response.sceneChange.continueSceneSound = kContinueSceneSound;
|
||||
}
|
||||
|
||||
// No scene branches
|
||||
uint16 numFlagsStructs = stream.readUint16LE();
|
||||
_flagsStructs.resize(numFlagsStructs);
|
||||
for (uint i = 0; i < numFlagsStructs; ++i) {
|
||||
FlagsStruct &flagsStruct = _flagsStructs[i];
|
||||
flagsStruct.conditions.read(stream);
|
||||
flagsStruct.flagToSet.type = stream.readByte();
|
||||
flagsStruct.flagToSet.flag.label = stream.readSint16LE();
|
||||
flagsStruct.flagToSet.flag.flag = stream.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationSound::readCaptionText(Common::SeekableReadStream &stream) {
|
||||
char *rawText = new char[1500];
|
||||
stream.read(rawText, 1500);
|
||||
assembleTextLine(rawText, _text, 1500);
|
||||
delete[] rawText;
|
||||
}
|
||||
|
||||
void ConversationSound::readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
|
||||
char *rawText = new char[400];
|
||||
stream.read(rawText, 400);
|
||||
assembleTextLine(rawText, response.text, 400);
|
||||
delete[] rawText;
|
||||
}
|
||||
|
||||
void ConversationSound::readTerseCaptionText(Common::SeekableReadStream &stream) {
|
||||
Common::String key;
|
||||
readFilename(stream, key);
|
||||
|
||||
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
|
||||
assert(convo);
|
||||
|
||||
_text = convo->texts[key];
|
||||
}
|
||||
|
||||
void ConversationSound::readTerseResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
|
||||
Common::String key;
|
||||
readFilename(stream, key);
|
||||
|
||||
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
|
||||
assert(convo);
|
||||
|
||||
response.text = convo->texts[key];
|
||||
}
|
||||
|
||||
void ConversationSound::execute() {
|
||||
ConversationSound *activeConversation = NancySceneState.getActiveConversation();
|
||||
if (activeConversation != this && activeConversation != nullptr) {
|
||||
if ( !activeConversation->_isDone ||
|
||||
activeConversation->_defaultNextScene == kDefaultNextSceneEnabled ||
|
||||
activeConversation->_pickedResponse != -1 ) {
|
||||
|
||||
return;
|
||||
} else {
|
||||
// Chained videos, hide the previous one and start this
|
||||
activeConversation->setVisible(false);
|
||||
NancySceneState.setActiveConversation(this);
|
||||
}
|
||||
}
|
||||
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_sound);
|
||||
|
||||
if (!ConfMan.getBool("speech_mute") && ConfMan.getBool("character_speech")) {
|
||||
g_nancy->_sound->playSound(_sound);
|
||||
}
|
||||
|
||||
// Remove held item and re-add it to inventory
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
// Move the mouse to the default position defined in CURS
|
||||
const Common::Point initialMousePos = g_nancy->_cursor->getPrimaryVideoInitialPos();
|
||||
const Common::Point cursorHotspot = g_nancy->_cursor->getCurrentCursorHotspot();
|
||||
Common::Point adjustedMousePos = g_nancy->_input->getInput().mousePos;
|
||||
adjustedMousePos.x -= cursorHotspot.x;
|
||||
adjustedMousePos.y -= cursorHotspot.y - 1;
|
||||
if (g_nancy->_cursor->getPrimaryVideoInactiveZone().bottom > adjustedMousePos.y) {
|
||||
g_nancy->_cursor->warpCursor(Common::Point(initialMousePos.x + cursorHotspot.x, initialMousePos.y + cursorHotspot.y));
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kNormalArrow);
|
||||
}
|
||||
|
||||
_state = kRun;
|
||||
NancySceneState.setActiveConversation(this);
|
||||
|
||||
// Do not draw first frame since video won't be loaded yet
|
||||
g_nancy->_graphics->suppressNextDraw();
|
||||
|
||||
if (g_nancy->getGameType() < kGameTypeNancy6) {
|
||||
// Do not fall through to give the execution one loop for event flag changes
|
||||
// This fixes TVD scene 750
|
||||
break;
|
||||
}
|
||||
|
||||
// However, nancy6 scene 1299 requires us to fall through in order to get the correct caption.
|
||||
// By that point Conversation scenes weren't the tangled mess they were in earlier games,
|
||||
// so hopefully this won't break anything
|
||||
}
|
||||
// fall through
|
||||
case kRun:
|
||||
if (!_hasDrawnTextbox) {
|
||||
_hasDrawnTextbox = true;
|
||||
auto *textboxData = GetEngineData(TBOX);
|
||||
assert(textboxData);
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().setOverrideFont(textboxData->conversationFontID);
|
||||
|
||||
if (ConfMan.getBool("subtitles")) {
|
||||
NancySceneState.getTextbox().addTextLine(_text);
|
||||
}
|
||||
|
||||
Common::Array<uint> responsesToAdd;
|
||||
for (uint i = 0; i < _responses.size(); ++i) {
|
||||
auto &res = _responses[i];
|
||||
|
||||
if (res.conditionFlags.isSatisfied()) {
|
||||
int foundIndex = -1;
|
||||
for (uint j = 0; j < responsesToAdd.size(); ++j) {
|
||||
if (_responses[responsesToAdd[j]].text.compareToIgnoreCase(res.text) == 0) {
|
||||
foundIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch(res.addRule) {
|
||||
case ResponseStruct::kAddIfNotFound:
|
||||
if (foundIndex == -1) {
|
||||
responsesToAdd.push_back(i);
|
||||
}
|
||||
|
||||
break;
|
||||
case ResponseStruct::kRemoveAndAddToEnd:
|
||||
if (foundIndex != -1) {
|
||||
responsesToAdd.remove_at(foundIndex);
|
||||
}
|
||||
|
||||
responsesToAdd.push_back(i);
|
||||
break;
|
||||
case ResponseStruct::kRemove:
|
||||
if (foundIndex != -1) {
|
||||
responsesToAdd.remove_at(foundIndex);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint numRegularResponses = _responses.size();
|
||||
|
||||
// Add responses when conditions have been satisfied
|
||||
if (_conditionalResponseCharacterID != _noResponse) {
|
||||
addConditionalDialogue();
|
||||
}
|
||||
|
||||
if (_goodbyeResponseCharacterID != _noResponse) {
|
||||
addGoodbye();
|
||||
}
|
||||
|
||||
// If conditionals/goodbyes added, make sure to send them to Textbox
|
||||
for (uint i = numRegularResponses; i < _responses.size(); ++i) {
|
||||
responsesToAdd.push_back(i);
|
||||
}
|
||||
|
||||
for (uint i : responsesToAdd) {
|
||||
NancySceneState.getTextbox().addTextLine(_responses[i].text);
|
||||
_responses[i].isOnScreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_sound) && isVideoDonePlaying()) {
|
||||
g_nancy->_sound->stopSound(_sound);
|
||||
|
||||
bool hasResponses = false;
|
||||
for (auto &res : _responses) {
|
||||
if (res.isOnScreen) {
|
||||
hasResponses = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasResponses) {
|
||||
// NPC has finished talking with no responses available, auto-advance to next scene
|
||||
_state = kActionTrigger;
|
||||
} else {
|
||||
// NPC has finished talking, we have responses
|
||||
for (uint i = 0; i < 30; ++i) {
|
||||
if (NancySceneState.getLogicCondition(i, g_nancy->_true)) {
|
||||
_pickedResponse = i;
|
||||
|
||||
// Adjust to account for hidden responses
|
||||
for (uint j = 0; j < _responses.size(); ++j) {
|
||||
if (!_responses[j].isOnScreen) {
|
||||
++_pickedResponse;
|
||||
}
|
||||
|
||||
if ((int)j == _pickedResponse) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedResponse != -1) {
|
||||
// Player has picked response, play sound file and change state
|
||||
_responseGenericSound.name = _responses[_pickedResponse].soundName;
|
||||
g_nancy->_sound->loadSound(_responseGenericSound);
|
||||
|
||||
if (!ConfMan.getBool("speech_mute") && ConfMan.getBool("player_speech")) {
|
||||
g_nancy->_sound->playSound(_responseGenericSound);
|
||||
}
|
||||
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_responseGenericSound)) {
|
||||
// process flags structs
|
||||
for (auto &flags : _flagsStructs) {
|
||||
if (flags.conditions.isSatisfied()) {
|
||||
flags.flagToSet.set();
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedResponse != -1) {
|
||||
// Set response's event flag, if any
|
||||
NancySceneState.setEventFlag(_responses[_pickedResponse].flagDesc);
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_responseGenericSound);
|
||||
|
||||
if (_pickedResponse != -1) {
|
||||
NancySceneState.changeScene(_responses[_pickedResponse].sceneChange);
|
||||
} else {
|
||||
for (uint i = 0; i < _sceneBranchStructs.size(); ++i) {
|
||||
if (_sceneBranchStructs[i].conditions.isSatisfied()) {
|
||||
NancySceneState.changeScene(_sceneBranchStructs[i].sceneChange);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_defaultNextScene == kDefaultNextSceneEnabled) {
|
||||
NancySceneState.changeScene(_sceneChange);
|
||||
} else if (_popNextScene == kPopNextScene) {
|
||||
// Exit dialogue
|
||||
NancySceneState.popScene();
|
||||
}
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationSound::addConditionalDialogue() {
|
||||
for (const auto &res : g_nancy->getStaticData().conditionalDialogue[_conditionalResponseCharacterID]) {
|
||||
bool isSatisfied = true;
|
||||
|
||||
for (const auto &cond : res.conditions) {
|
||||
switch (cond.type) {
|
||||
case (byte)StaticDataConditionType::kEvent :
|
||||
if (!NancySceneState.getEventFlag(cond.label, cond.flag)) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case (byte)StaticDataConditionType::kInventory :
|
||||
if (NancySceneState.hasItem(cond.label) != cond.flag) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case (byte)StaticDataConditionType::kDifficulty :
|
||||
if ( (NancySceneState.getDifficulty() != cond.label && cond.flag != 0) ||
|
||||
(NancySceneState.getDifficulty() == cond.label && cond.flag == 0) ) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isSatisfied) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSatisfied) {
|
||||
_responses.push_back(ResponseStruct());
|
||||
ResponseStruct &newResponse = _responses.back();
|
||||
newResponse.soundName = res.soundID;
|
||||
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy5) {
|
||||
// String is also inside nancy.dat
|
||||
newResponse.text = g_nancy->getStaticData().conditionalDialogueTexts[res.textID];
|
||||
} else {
|
||||
// String is inside the CVTX chunk in the CONVO file. Sound ID doubles as string key
|
||||
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
|
||||
assert(convo);
|
||||
|
||||
newResponse.text = convo->texts[res.soundID];
|
||||
}
|
||||
|
||||
newResponse.sceneChange.sceneID = res.sceneID;
|
||||
newResponse.sceneChange.continueSceneSound = kContinueSceneSound;
|
||||
newResponse.sceneChange.listenerFrontVector.set(0, 0, 1);
|
||||
|
||||
// Check if the response is a repeat. This can happen when multiple condition combinations
|
||||
// trigger the same response.
|
||||
for (uint i = 0; i < _responses.size() - 1; ++i) {
|
||||
if ( _responses[i].soundName == newResponse.soundName &&
|
||||
_responses[i].text == newResponse.text &&
|
||||
_responses[i].sceneChange.sceneID == newResponse.sceneChange.sceneID) {
|
||||
_responses.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationSound::addGoodbye() {
|
||||
auto &res = g_nancy->getStaticData().goodbyes[_goodbyeResponseCharacterID];
|
||||
_responses.push_back(ResponseStruct());
|
||||
ResponseStruct &newResponse = _responses.back();
|
||||
newResponse.soundName = res.soundID;
|
||||
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy5) {
|
||||
// String is also inside nancy.dat
|
||||
newResponse.text = g_nancy->getStaticData().goodbyeTexts[_goodbyeResponseCharacterID];
|
||||
} else {
|
||||
// String is inside the CVTX chunk in the CONVO file. Sound ID doubles as string key
|
||||
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
|
||||
assert(convo);
|
||||
|
||||
newResponse.text = convo->texts[res.soundID];
|
||||
}
|
||||
|
||||
// Evaluate conditions to pick from the collection of replies
|
||||
uint sceneChangeID = 0;
|
||||
for (uint i = 0; i < res.sceneChanges.size(); ++i) {
|
||||
const GoodbyeSceneChange &sc = res.sceneChanges[i];
|
||||
if (sc.conditions.size() == 0) {
|
||||
// No conditions, default choice
|
||||
sceneChangeID = i;
|
||||
break;
|
||||
} else {
|
||||
bool isSatisfied = true;
|
||||
|
||||
for (const auto &cond : sc.conditions) {
|
||||
switch (cond.type) {
|
||||
case (byte)StaticDataConditionType::kEvent :
|
||||
if (!NancySceneState.getEventFlag(cond.label, cond.flag)) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case (byte)StaticDataConditionType::kInventory :
|
||||
if (NancySceneState.hasItem(cond.label) != cond.flag) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case (byte)StaticDataConditionType::kDifficulty :
|
||||
if ( (NancySceneState.getDifficulty() != cond.label && cond.flag != 0) ||
|
||||
(NancySceneState.getDifficulty() == cond.label && cond.flag == 0) ) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isSatisfied) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSatisfied) {
|
||||
sceneChangeID = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GoodbyeSceneChange &sceneChange = res.sceneChanges[sceneChangeID];
|
||||
|
||||
// The reply from the character is picked randomly
|
||||
newResponse.sceneChange.sceneID = sceneChange.sceneIDs[g_nancy->_randomSource->getRandomNumber(sceneChange.sceneIDs.size() - 1)];
|
||||
newResponse.sceneChange.continueSceneSound = kContinueSceneSound;
|
||||
newResponse.sceneChange.listenerFrontVector.set(0, 0, 1);
|
||||
|
||||
// Set an event flag if applicable
|
||||
// Assumes flagToSet is an event flag
|
||||
NancySceneState.setEventFlag(sceneChange.flagToSet.label, sceneChange.flagToSet.flag);
|
||||
}
|
||||
|
||||
void ConversationSound::ConversationFlag::read(Common::SeekableReadStream &stream) {
|
||||
type = stream.readByte();
|
||||
flag.label = stream.readSint16LE();
|
||||
flag.flag = stream.readByte();
|
||||
orFlag = stream.readByte();
|
||||
}
|
||||
|
||||
bool ConversationSound::ConversationFlag::isSatisfied() const {
|
||||
switch (type) {
|
||||
case kFlagEvent:
|
||||
return NancySceneState.getEventFlag(flag);
|
||||
case kFlagInventory:
|
||||
return NancySceneState.hasItem(flag.label) == flag.flag;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationSound::ConversationFlag::set() const {
|
||||
switch (type) {
|
||||
case kFlagEvent:
|
||||
NancySceneState.setEventFlag(flag);
|
||||
break;
|
||||
case kFlagInventory:
|
||||
if (flag.flag == g_nancy->_true) {
|
||||
NancySceneState.addItemToInventory(flag.label);
|
||||
} else {
|
||||
NancySceneState.removeItemFromInventory(flag.label);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationSound::ConversationFlags::read(Common::SeekableReadStream &stream) {
|
||||
uint16 numFlags = stream.readUint16LE();
|
||||
|
||||
conditionFlags.resize(numFlags);
|
||||
for (uint i = 0; i < numFlags; ++i) {
|
||||
conditionFlags[i].read(stream);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConversationSound::ConversationFlags::isSatisfied() const {
|
||||
Common::Array<bool> conditionsMet(conditionFlags.size(), false);
|
||||
|
||||
for (uint i = 0; i < conditionFlags.size(); ++i) {
|
||||
if (conditionFlags[i].isSatisfied()) {
|
||||
conditionsMet[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < conditionsMet.size(); ++i) {
|
||||
if (conditionFlags[i].orFlag) {
|
||||
bool foundSatisfied = false;
|
||||
for (uint j = 0; j < conditionFlags.size(); ++j) {
|
||||
if (conditionsMet[j]) {
|
||||
foundSatisfied = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Found end of orFlag chain
|
||||
if (!conditionFlags[j].orFlag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundSatisfied) {
|
||||
for (; i < conditionsMet.size(); ++i) {
|
||||
conditionsMet[i] = true;
|
||||
if (!conditionFlags[i].orFlag) {
|
||||
// End of orFlag chain
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < conditionsMet.size(); ++i) {
|
||||
if (conditionsMet[i] == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConversationVideo::init() {
|
||||
if (!_decoder.loadFile(Common::Path(_videoName + ".avf"))) {
|
||||
error("Couldn't load video file %s", _videoName.c_str());
|
||||
}
|
||||
|
||||
_decoder.seekToFrame(_firstFrame);
|
||||
|
||||
if (!_paletteName.empty()) {
|
||||
GraphicsManager::loadSurfacePalette(_drawSurface, _paletteName);
|
||||
setTransparent(true);
|
||||
}
|
||||
|
||||
ConversationSound::init();
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void ConversationVideo::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Serializer ser(&stream, nullptr);
|
||||
ser.setVersion(g_nancy->getGameType());
|
||||
|
||||
readFilename(stream, _videoName);
|
||||
readFilename(ser, _paletteName, kGameTypeVampire, kGameTypeVampire);
|
||||
|
||||
ser.skip(2, kGameTypeVampire, kGameTypeNancy1);
|
||||
ser.syncAsUint16LE(_videoFormat);
|
||||
ser.skip(3); // Quality
|
||||
ser.skip(4, kGameTypeVampire, kGameTypeVampire); // paletteStart, paletteSize
|
||||
ser.syncAsUint16LE(_firstFrame);
|
||||
ser.syncAsUint16LE(_lastFrame);
|
||||
ser.skip(8, kGameTypeVampire, kGameTypeNancy1);
|
||||
ser.skip(6, kGameTypeNancy2);
|
||||
|
||||
ser.skip(0x10); // Bounds
|
||||
readRect(stream, _screenPosition);
|
||||
|
||||
ConversationSound::readData(stream);
|
||||
}
|
||||
|
||||
void ConversationVideo::updateGraphics() {
|
||||
if (!_decoder.isVideoLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_decoder.isPlaying()) {
|
||||
_decoder.start();
|
||||
}
|
||||
|
||||
if (_decoder.getCurFrame() == _lastFrame) {
|
||||
_decoder.pauseVideo(true);
|
||||
}
|
||||
|
||||
if (_decoder.needsUpdate()) {
|
||||
GraphicsManager::copyToManaged(*_decoder.decodeNextFrame(), _drawSurface, _videoFormat == kSmallVideoFormat);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
RenderObject::updateGraphics();
|
||||
}
|
||||
|
||||
void ConversationVideo::onPause(bool pause) {
|
||||
_decoder.pauseVideo(pause);
|
||||
RenderActionRecord::onPause(pause);
|
||||
}
|
||||
|
||||
bool ConversationVideo::isVideoDonePlaying() {
|
||||
return _decoder.endOfVideo() || _decoder.getCurFrame() == _lastFrame;
|
||||
}
|
||||
|
||||
Common::String ConversationVideo::getRecordTypeName() const {
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy1) {
|
||||
return "PlayPrimaryVideo";
|
||||
} else {
|
||||
return "ConversationVideo";
|
||||
}
|
||||
}
|
||||
|
||||
class ConversationCelLoader : public DeferredLoader {
|
||||
public:
|
||||
ConversationCelLoader(ConversationCel &owner) : _owner(owner) {}
|
||||
|
||||
private:
|
||||
bool loadInner() override;
|
||||
|
||||
ConversationCel &_owner;
|
||||
};
|
||||
|
||||
bool ConversationCelLoader::loadInner() {
|
||||
for (uint i = _owner._curFrame; i < _owner._celNames[0].size(); ++i) {
|
||||
for (uint j = 0; j < _owner._celRObjects.size(); ++j) {
|
||||
if (!_owner._celCache.contains(_owner._celNames[j][i])) {
|
||||
_owner.loadCel(_owner._celNames[j][i], _owner._treeNames[j]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ConversationCel::~ConversationCel() {
|
||||
// Make sure there isn't a single-frame gap between conversation scenes where
|
||||
// the character is invisible
|
||||
g_nancy->_graphics->suppressNextDraw();
|
||||
}
|
||||
|
||||
void ConversationCel::init() {
|
||||
_curFrame = _firstFrame;
|
||||
_nextFrameTime = g_nancy->getTotalPlayTime();
|
||||
ConversationSound::init();
|
||||
|
||||
_loaderPtr.reset(new ConversationCelLoader(*this));
|
||||
auto castedPtr = _loaderPtr.dynamicCast<DeferredLoader>();
|
||||
g_nancy->addDeferredLoader(castedPtr);
|
||||
|
||||
for (uint i = 0; i < _treeNames.size(); ++i) {
|
||||
if (_treeNames[i].size()) {
|
||||
_celRObjects.push_back(RenderedCel());
|
||||
} else break;
|
||||
}
|
||||
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void ConversationCel::registerGraphics() {
|
||||
for (uint i = 0; i < _celRObjects.size(); ++i) {
|
||||
_celRObjects[i]._z = 9 + _drawingOrder[i];
|
||||
_celRObjects[i].setVisible(true);
|
||||
_celRObjects[i].setTransparent(true);
|
||||
_celRObjects[i].registerGraphics();
|
||||
}
|
||||
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void ConversationCel::updateGraphics() {
|
||||
uint32 currentTime = g_nancy->getTotalPlayTime();
|
||||
|
||||
if (_state == kRun && currentTime > _nextFrameTime && _curFrame < MIN<uint>(_lastFrame + 1, _celNames[0].size())) {
|
||||
for (uint i = 0; i < _celRObjects.size(); ++i) {
|
||||
Cel &cel = loadCel(_celNames[i][_curFrame], _treeNames[i]);
|
||||
if (_overrideTreeRects[i] == kCelOverrideTreeRectsOn) {
|
||||
_celRObjects[i]._drawSurface.create(cel.surf, _overrideRectSrcs[i]);
|
||||
_celRObjects[i].moveTo(_overrideRectDests[i]);
|
||||
} else {
|
||||
_celRObjects[i]._drawSurface.create(cel.surf, cel.src);
|
||||
_celRObjects[i].moveTo(cel.dest);
|
||||
}
|
||||
}
|
||||
|
||||
_nextFrameTime += _frameTime;
|
||||
++_curFrame;
|
||||
}
|
||||
}
|
||||
|
||||
void ConversationCel::readData(Common::SeekableReadStream &stream) {
|
||||
Common::String xsheetName;
|
||||
readFilename(stream, xsheetName);
|
||||
|
||||
readFilenameArray(stream, _treeNames, 4);
|
||||
readXSheet(stream, xsheetName);
|
||||
|
||||
// Continue reading the AR stream
|
||||
|
||||
// Something related to quality
|
||||
stream.skip(3);
|
||||
|
||||
_firstFrame = stream.readUint16LE();
|
||||
_lastFrame = stream.readUint16LE();
|
||||
|
||||
stream.skip(6);
|
||||
|
||||
_drawingOrder.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_drawingOrder[i] = stream.readByte();
|
||||
}
|
||||
|
||||
_overrideTreeRects.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_overrideTreeRects[i] = stream.readByte();
|
||||
}
|
||||
|
||||
readRectArray(stream, _overrideRectSrcs, 4);
|
||||
readRectArray(stream, _overrideRectDests, 4);
|
||||
|
||||
ConversationSound::readData(stream);
|
||||
}
|
||||
|
||||
void ConversationCel::readXSheet(Common::SeekableReadStream &stream, const Common::String &xsheetName) {
|
||||
Common::SeekableReadStream *xsheet = SearchMan.createReadStreamForMember(Common::Path(xsheetName));
|
||||
|
||||
// Read the xsheet and load all images into the arrays
|
||||
// Completely unoptimized, the original engine uses a buffer
|
||||
xsheet->seek(0);
|
||||
Common::String signature = xsheet->readString('\0', 18);
|
||||
if (signature != "XSHEET WayneSikes") {
|
||||
warning("XSHEET signature doesn't match!");
|
||||
return;
|
||||
}
|
||||
|
||||
xsheet->seek(0x22);
|
||||
uint numFrames = xsheet->readUint16LE();
|
||||
xsheet->skip(2);
|
||||
_frameTime = xsheet->readUint16LE();
|
||||
xsheet->skip(2);
|
||||
|
||||
_celNames.resize(4, Common::Array<Common::Path>(numFrames));
|
||||
for (uint i = 0; i < numFrames; ++i) {
|
||||
for (uint j = 0; j < _celNames.size(); ++j) {
|
||||
readFilename(*xsheet, _celNames[j][i]);
|
||||
}
|
||||
|
||||
// 4 unknown values
|
||||
xsheet->skip(8);
|
||||
}
|
||||
|
||||
delete xsheet;
|
||||
}
|
||||
|
||||
bool ConversationCel::isVideoDonePlaying() {
|
||||
return _curFrame >= MIN<uint>(_lastFrame, _celNames[0].size()) && _nextFrameTime <= g_nancy->getTotalPlayTime();
|
||||
}
|
||||
|
||||
ConversationCel::Cel &ConversationCel::loadCel(const Common::Path &name, const Common::String &treeName) {
|
||||
// Assumes head and body cels will be named differently
|
||||
if (_celCache.contains(name)) {
|
||||
return _celCache[name];
|
||||
}
|
||||
|
||||
Cel &newCel = _celCache.getOrCreateVal(name);
|
||||
g_nancy->_resource->loadImage(name, newCel.surf, treeName, &newCel.src, &newCel.dest);
|
||||
return _celCache[name];
|
||||
}
|
||||
|
||||
void ConversationSoundTerse::readData(Common::SeekableReadStream &stream) {
|
||||
readTerseData(stream);
|
||||
}
|
||||
|
||||
void ConversationCelTerse::readData(Common::SeekableReadStream &stream) {
|
||||
Common::String xsheetName;
|
||||
readFilename(stream, xsheetName);
|
||||
|
||||
readFilenameArray(stream, _treeNames, 2); // Only 2
|
||||
readXSheet(stream, xsheetName);
|
||||
|
||||
_lastFrame = stream.readUint16LE();
|
||||
_drawingOrder = { 1, 0, 2, 3 };
|
||||
_overrideTreeRects.resize(4, kCelOverrideTreeRectsOff);
|
||||
|
||||
readTerseData(stream);
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
261
engines/nancy/action/conversation.h
Normal file
261
engines/nancy/action/conversation.h
Normal file
@@ -0,0 +1,261 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_CONVERSATION_H
|
||||
#define NANCY_ACTION_CONVERSATION_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/video.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// The base class for conversations, with no video data. Contains the following:
|
||||
// - a base sound for the NPC's speech and its caption (mandatory)
|
||||
// - a list of possible player responses, also with sounds and captions (optional)
|
||||
// Captions are displayed in the Textbox, and player responses are also selectable there.
|
||||
// Captions are hypertext; meaning, they contain extra data related to the text (see misc/hypertext.h)
|
||||
// A conversation will auto-advance to a next scene when no responses are available; the next scene
|
||||
// can either be described within the Conversation data, or can be whatever's pushed onto the scene "stack".
|
||||
// Also supports branching scenes depending on a condition, though that is only used in older games.
|
||||
// Player responses can also be conditional; the original engine had special-purpose "infocheck"
|
||||
// functions, two per character ID, which were used to evaluate those conditions. We replace that with
|
||||
// the data bundled inside nancy.dat (see devtools/create_nancy).
|
||||
class ConversationSound : public RenderActionRecord {
|
||||
public:
|
||||
ConversationSound();
|
||||
virtual ~ConversationSound();
|
||||
|
||||
void init() override;
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
virtual bool isVideoDonePlaying() { return true; }
|
||||
|
||||
protected:
|
||||
struct ConversationFlag {
|
||||
byte type;
|
||||
FlagDescription flag;
|
||||
byte orFlag;
|
||||
|
||||
void read(Common::SeekableReadStream &stream);
|
||||
bool isSatisfied() const;
|
||||
void set() const;
|
||||
};
|
||||
|
||||
struct ConversationFlags {
|
||||
Common::Array<ConversationFlag> conditionFlags;
|
||||
|
||||
void read(Common::SeekableReadStream &stream);
|
||||
bool isSatisfied() const;
|
||||
};
|
||||
|
||||
struct ResponseStruct {
|
||||
enum AddRule { kAddIfNotFound, kRemoveAndAddToEnd, kRemove };
|
||||
|
||||
ConversationFlags conditionFlags;
|
||||
Common::String text;
|
||||
Common::String soundName;
|
||||
byte addRule = kAddIfNotFound;
|
||||
SceneChangeDescription sceneChange;
|
||||
FlagDescription flagDesc;
|
||||
|
||||
bool isOnScreen = false;
|
||||
};
|
||||
|
||||
struct FlagsStruct {
|
||||
ConversationFlags conditions;
|
||||
ConversationFlag flagToSet;
|
||||
};
|
||||
|
||||
struct SceneBranchStruct {
|
||||
ConversationFlags conditions;
|
||||
SceneChangeDescription sceneChange;
|
||||
};
|
||||
|
||||
static const byte kDefaultNextSceneEnabled = 1;
|
||||
static const byte kDefaultNextSceneDisabled = 2;
|
||||
|
||||
static const byte kPopNextScene = 1;
|
||||
static const byte kNoPopNextScene = 2;
|
||||
|
||||
Common::String getRecordTypeName() const override { return "ConversationSound"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
// Functions for reading captions are virtual to allow easier support for the terse Conversation variants
|
||||
virtual void readCaptionText(Common::SeekableReadStream &stream);
|
||||
virtual void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response);
|
||||
|
||||
// Used in subclasses
|
||||
void readTerseData(Common::SeekableReadStream &stream);
|
||||
void readTerseCaptionText(Common::SeekableReadStream &stream);
|
||||
void readTerseResponseText(Common::SeekableReadStream &stream, ResponseStruct &response);
|
||||
|
||||
// Functions for handling the built-in dialogue responses found in the executable
|
||||
void addConditionalDialogue();
|
||||
void addGoodbye();
|
||||
|
||||
Common::String _text;
|
||||
|
||||
SoundDescription _sound;
|
||||
SoundDescription _responseGenericSound;
|
||||
|
||||
byte _conditionalResponseCharacterID;
|
||||
byte _goodbyeResponseCharacterID;
|
||||
byte _defaultNextScene = kDefaultNextSceneEnabled;
|
||||
byte _popNextScene = kNoPopNextScene;
|
||||
SceneChangeDescription _sceneChange;
|
||||
|
||||
Common::Array<ResponseStruct> _responses;
|
||||
Common::Array<FlagsStruct> _flagsStructs;
|
||||
Common::Array<SceneBranchStruct> _sceneBranchStructs;
|
||||
|
||||
bool _hasDrawnTextbox;
|
||||
int16 _pickedResponse;
|
||||
|
||||
const byte _noResponse;
|
||||
};
|
||||
|
||||
// Conversation with an AVF video. Originally called PlayPrimaryVideoChan0
|
||||
class ConversationVideo : public ConversationSound {
|
||||
public:
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
void onPause(bool pause) override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
bool isVideoDonePlaying() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override;
|
||||
|
||||
Common::String _videoName;
|
||||
Common::Path _paletteName;
|
||||
uint _videoFormat = kLargeVideoFormat;
|
||||
uint16 _firstFrame = 0;
|
||||
int16 _lastFrame = 0;
|
||||
AVFDecoder _decoder;
|
||||
};
|
||||
|
||||
class ConversationCelLoader;
|
||||
|
||||
// Conversation with separate cels for the body and head of the character.
|
||||
// Cels are separate images bundled inside a .cal file
|
||||
class ConversationCel : public ConversationSound {
|
||||
friend class ConversationCelLoader;
|
||||
public:
|
||||
ConversationCel() {}
|
||||
virtual ~ConversationCel();
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ConversationCel"; }
|
||||
|
||||
struct Cel {
|
||||
Graphics::ManagedSurface surf;
|
||||
Common::Rect src;
|
||||
Common::Rect dest;
|
||||
};
|
||||
|
||||
class RenderedCel : public RenderObject {
|
||||
friend class ConversationCel;
|
||||
public:
|
||||
RenderedCel() : RenderObject(9) {}
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
static const byte kCelOverrideTreeRectsOff = 1;
|
||||
static const byte kCelOverrideTreeRectsOn = 2;
|
||||
|
||||
bool isVideoDonePlaying() override;
|
||||
Cel &loadCel(const Common::Path &name, const Common::String &treeName);
|
||||
|
||||
void readXSheet(Common::SeekableReadStream &stream, const Common::String &xsheetName);
|
||||
|
||||
Common::Array<Common::Array<Common::Path>> _celNames;
|
||||
Common::Array<Common::String> _treeNames;
|
||||
|
||||
uint16 _frameTime = 0;
|
||||
uint _videoFormat = kLargeVideoFormat;
|
||||
uint16 _firstFrame = 0;
|
||||
uint16 _lastFrame = 0;
|
||||
|
||||
Common::Array<byte> _drawingOrder;
|
||||
Common::Array<byte> _overrideTreeRects;
|
||||
|
||||
Common::Array<Common::Rect> _overrideRectSrcs;
|
||||
Common::Array<Common::Rect> _overrideRectDests;
|
||||
|
||||
uint _curFrame = 0;
|
||||
uint32 _nextFrameTime = 0;
|
||||
|
||||
Common::Array<RenderedCel> _celRObjects;
|
||||
|
||||
Common::HashMap<Common::Path, Cel, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> _celCache;
|
||||
Common::SharedPtr<ConversationCelLoader> _loaderPtr;
|
||||
};
|
||||
|
||||
// A ConversationSound without embedded text; uses the CONVO chunk instead
|
||||
class ConversationSoundT : public ConversationSound {
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ConversationSoundT"; }
|
||||
|
||||
void readCaptionText(Common::SeekableReadStream &stream) override { readTerseCaptionText(stream); }
|
||||
void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) override { readTerseResponseText(stream, response); }
|
||||
};
|
||||
|
||||
// A ConversationCel without embedded text; uses the CONVO chunk instead
|
||||
class ConversationCelT : public ConversationCel {
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ConversationCelT"; }
|
||||
|
||||
void readCaptionText(Common::SeekableReadStream &stream) override { readTerseCaptionText(stream); }
|
||||
void readResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) override { readTerseResponseText(stream, response); }
|
||||
};
|
||||
|
||||
// A ConversationSound with a much smaller data footprint
|
||||
class ConversationSoundTerse : public ConversationSound {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ConversationSoundTerse"; }
|
||||
};
|
||||
|
||||
// A ConversationCel with a much smaller data footprint
|
||||
class ConversationCelTerse : public ConversationCel {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ConversationCelTerse"; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_CONVERSATION_H
|
||||
467
engines/nancy/action/datarecords.cpp
Normal file
467
engines/nancy/action/datarecords.cpp
Normal file
@@ -0,0 +1,467 @@
|
||||
/* 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
|
||||
174
engines/nancy/action/datarecords.h
Normal file
174
engines/nancy/action/datarecords.h
Normal file
@@ -0,0 +1,174 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_DATARECORDS_H
|
||||
#define NANCY_ACTION_DATARECORDS_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
class NancyEngine;
|
||||
|
||||
namespace Action {
|
||||
|
||||
// Changes the selected value inside the TableData. Value can be incremented, decremented, or not changed.
|
||||
// Also responsible for checking whether all values are correct (as described in the TABL chunk). nancy6 only.
|
||||
class TableIndexSetValueHS : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
CursorManager::CursorType getHoverCursor() const override { return (CursorManager::CursorType)_cursorType; }
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TableIndexSetValueHS"; }
|
||||
|
||||
uint16 _tableIndex = 0;
|
||||
byte _valueChangeType = kNoChangeTableValue;
|
||||
int16 _entryCorrectFlagID = -1;
|
||||
int16 _allEntriesCorrectFlagID = -1;
|
||||
|
||||
MultiEventFlagDescription _flags;
|
||||
uint16 _cursorType = 1;
|
||||
Common::Array<HotspotDescription> _hotspots;
|
||||
};
|
||||
|
||||
// Sets (or adds to) a value inside the TableData struct
|
||||
class SetValue : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SetValue"; }
|
||||
|
||||
byte _index = 0;
|
||||
bool _shouldSet = false;
|
||||
int16 _value = kNoTableValue;
|
||||
};
|
||||
|
||||
class SetValueCombo : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SetValueCombo"; }
|
||||
|
||||
byte _valueIndex = 0;
|
||||
Common::Array<byte> _indices;
|
||||
Common::Array<int16> _percentages;
|
||||
};
|
||||
|
||||
class ValueTest : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ValueTest"; }
|
||||
|
||||
byte _valueIndex = 0;
|
||||
byte _testType = 0;
|
||||
byte _condition = 0;
|
||||
Common::Array<byte> _indicesToTest;
|
||||
|
||||
int16 _flagToSet = kFlagNoLabel;
|
||||
};
|
||||
|
||||
// Sets up to 10 flags at once.
|
||||
class EventFlags : public ActionRecord {
|
||||
public:
|
||||
EventFlags(bool terse = false) : _isTerse(terse) {}
|
||||
virtual ~EventFlags() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
MultiEventFlagDescription _flags;
|
||||
bool _isTerse;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return _isTerse ? "EventFlagsTerse" : "EventFlags"; }
|
||||
};
|
||||
|
||||
// Sets up to 10 flags when clicked. Hotspot can move alongside background frame.
|
||||
class EventFlagsMultiHS : public EventFlags {
|
||||
public:
|
||||
EventFlagsMultiHS(bool isCursor, bool terse = false) : EventFlags(terse), _isCursor(isCursor) {}
|
||||
virtual ~EventFlagsMultiHS() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
CursorManager::CursorType getHoverCursor() const override { return _hoverCursor; }
|
||||
|
||||
CursorManager::CursorType _hoverCursor = CursorManager::kHotspot;
|
||||
Common::Array<HotspotDescription> _hotspots;
|
||||
|
||||
bool _isCursor;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override { return _isCursor ? (_isTerse ? "EventFlagsHSTerse" : "EventFlagsCursorHS") : "EventFlagsMultiHS"; }
|
||||
};
|
||||
|
||||
// Sets the difficulty level for the current save. Only appears at the start of the game.
|
||||
// First appears in nancy1. Nancy1 and nancy2 have three difficulty values, while later games
|
||||
// only have two: 0 and 2.
|
||||
class DifficultyLevel : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
uint16 _difficulty = 0;
|
||||
FlagDescription _flag;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "DifficultyLevel"; }
|
||||
};
|
||||
|
||||
class ModifyListEntry : public ActionRecord {
|
||||
public:
|
||||
enum Type { kAdd, kDelete, kMark };
|
||||
|
||||
ModifyListEntry(Type type) : _type(type) {}
|
||||
virtual ~ModifyListEntry() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
Type _type;
|
||||
|
||||
uint16 _surfaceID = 0;
|
||||
Common::String _stringID;
|
||||
uint16 _mark = 0;
|
||||
uint16 _sceneID = kNoScene;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_DATARECORDS_H
|
||||
148
engines/nancy/action/interactivevideo.cpp
Normal file
148
engines/nancy/action/interactivevideo.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
/* 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/resource.h"
|
||||
#include "engines/nancy/video.h"
|
||||
#include "engines/nancy/input.h"
|
||||
|
||||
#include "engines/nancy/action/interactivevideo.h"
|
||||
#include "engines/nancy/action/secondarymovie.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void InteractiveVideo::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Path ivFilename;
|
||||
readFilename(stream, ivFilename);
|
||||
|
||||
stream.skip(2);
|
||||
|
||||
_flags.resize(15);
|
||||
_cursors.resize(5);
|
||||
|
||||
for (uint i = 0; i < 15; ++i) {
|
||||
_flags[i].label = stream.readSint16LE();
|
||||
_flags[i].flag = stream.readSint16LE();
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 5; ++ i) {
|
||||
_cursors[i] = stream.readSint16LE();
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *ivFile = SearchMan.createReadStreamForMember(ivFilename.append(".iv"));
|
||||
assert(ivFile);
|
||||
|
||||
readFilename(*ivFile, _videoName);
|
||||
|
||||
uint32 numFrames = ivFile->readUint32LE();
|
||||
_frames.resize(numFrames);
|
||||
for (uint i = 0; i < numFrames; ++i) {
|
||||
InteractiveFrame &frame = _frames[i];
|
||||
frame.frameID = ivFile->readUint16LE();
|
||||
uint16 numHotspots = ivFile->readUint16LE();
|
||||
frame.triggerOnNoHotspot = ivFile->readByte();
|
||||
frame.noHSFlagID = ivFile->readSint16LE();
|
||||
frame.noHSCursorID = ivFile->readSint16LE();
|
||||
|
||||
frame.hotspots.resize(numHotspots);
|
||||
for (uint j = 0; j < numHotspots; ++j) {
|
||||
ivFile->skip(4);
|
||||
readRect(*ivFile, frame.hotspots[j].hotspot);
|
||||
frame.hotspots[j].flagID = ivFile->readSint16LE();
|
||||
frame.hotspots[j].cursorID = ivFile->readSint16LE();
|
||||
}
|
||||
}
|
||||
|
||||
delete ivFile;
|
||||
}
|
||||
|
||||
void InteractiveVideo::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_movieAR = NancySceneState.getActiveMovie();
|
||||
if (!_movieAR || _movieAR->_state == kRun) {
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
break;
|
||||
case kRun:
|
||||
if (_movieAR->_state == kActionTrigger || _movieAR->_isFinished) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
finishExecution();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void InteractiveVideo::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
int curFrame = _movieAR->_decoder->getCurFrame();
|
||||
if (curFrame < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &frame : _frames) {
|
||||
if (frame.frameID == curFrame) {
|
||||
// Found data for the current video frame
|
||||
|
||||
// First, look through the hotspots for the frame
|
||||
for (auto &hotspot : frame.hotspots) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(hotspot.hotspot).contains(input.mousePos)) {
|
||||
// Mouse is in a hotspot, change cursor and set flag if clicked
|
||||
if (hotspot.cursorID >= 0 && _cursors[hotspot.cursorID] >= 0) {
|
||||
g_nancy->_cursor->setCursorType((CursorManager::CursorType)_cursors[hotspot.cursorID]);
|
||||
}
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
NancySceneState.setEventFlag(_flags[hotspot.flagID]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse is not in a hotspot for the frame, check if we have a default action
|
||||
if (frame.triggerOnNoHotspot) {
|
||||
if (frame.noHSCursorID >= 0 && _cursors[frame.noHSCursorID] >= 0) {
|
||||
g_nancy->_cursor->setCursorType((CursorManager::CursorType)_cursors[frame.noHSCursorID]);
|
||||
}
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
NancySceneState.setEventFlag(_flags[frame.noHSFlagID]);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
76
engines/nancy/action/interactivevideo.h
Normal file
76
engines/nancy/action/interactivevideo.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_INTERACTIVEVIDEO_H
|
||||
#define NANCY_ACTION_INTERACTIVEVIDEO_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class ActionManager;
|
||||
class PlaySecondaryMovie;
|
||||
|
||||
class InteractiveVideo : public ActionRecord {
|
||||
friend class ActionManager;
|
||||
friend class PlaySecondaryMovie;
|
||||
public:
|
||||
InteractiveVideo() {}
|
||||
virtual ~InteractiveVideo() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "InteractiveVideo"; }
|
||||
|
||||
struct InteractiveHotspot {
|
||||
Common::Rect hotspot;
|
||||
int16 flagID = -1;
|
||||
int16 cursorID = -1;
|
||||
};
|
||||
|
||||
struct InteractiveFrame {
|
||||
uint16 frameID = 0;
|
||||
bool triggerOnNoHotspot = false;
|
||||
int16 noHSFlagID = -1;
|
||||
int16 noHSCursorID = -1;
|
||||
Common::Array<InteractiveHotspot> hotspots;
|
||||
};
|
||||
|
||||
Common::Array<FlagDescription> _flags;
|
||||
Common::Array<int16> _cursors;
|
||||
|
||||
// IV file data
|
||||
Common::Path _videoName;
|
||||
Common::Array<InteractiveFrame> _frames;
|
||||
|
||||
// Pointer to a movie AR
|
||||
PlaySecondaryMovie *_movieAR = nullptr;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_INTERACTIVEVIDEO_H
|
||||
232
engines/nancy/action/inventoryrecords.cpp
Normal file
232
engines/nancy/action/inventoryrecords.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
/* 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/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
|
||||
#include "engines/nancy/action/inventoryrecords.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void AddInventoryNoHS::readData(Common::SeekableReadStream &stream) {
|
||||
_itemID = stream.readUint16LE();
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy6) {
|
||||
_setCursor = stream.readUint16LE();
|
||||
_forceCursor = stream.readUint16LE();
|
||||
}
|
||||
}
|
||||
|
||||
void AddInventoryNoHS::execute() {
|
||||
if (_setCursor) {
|
||||
if (NancySceneState.getHeldItem() != -1) {
|
||||
// Currently holding another item
|
||||
if (_forceCursor) {
|
||||
NancySceneState.addItemToInventory(NancySceneState.getHeldItem());
|
||||
if (NancySceneState.hasItem(_itemID) == g_nancy->_true) {
|
||||
NancySceneState.removeItemFromInventory(_itemID, true);
|
||||
} else {
|
||||
NancySceneState.setHeldItem(_itemID);
|
||||
}
|
||||
} else {
|
||||
NancySceneState.addItemToInventory(_itemID);
|
||||
}
|
||||
} else {
|
||||
if (NancySceneState.hasItem(_itemID) == g_nancy->_true) {
|
||||
NancySceneState.removeItemFromInventory(_itemID, true);
|
||||
} else {
|
||||
NancySceneState.setHeldItem(_itemID);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (NancySceneState.hasItem(_itemID) == g_nancy->_false) {
|
||||
NancySceneState.addItemToInventory(_itemID);
|
||||
}
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void RemoveInventoryNoHS::readData(Common::SeekableReadStream &stream) {
|
||||
_itemID = stream.readUint16LE();
|
||||
}
|
||||
|
||||
void RemoveInventoryNoHS::execute() {
|
||||
if (NancySceneState.hasItem(_itemID) == g_nancy->_true) {
|
||||
NancySceneState.removeItemFromInventory(_itemID, false);
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void ShowInventoryItem::init() {
|
||||
g_nancy->_resource->loadImage(_imageName, _fullSurface);
|
||||
|
||||
_drawSurface.create(_fullSurface, _blitDescriptions[0].src);
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void ShowInventoryItem::readData(Common::SeekableReadStream &stream) {
|
||||
GameType gameType = g_nancy->getGameType();
|
||||
_objectID = stream.readUint16LE();
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint16 numFrames = stream.readUint16LE();
|
||||
if (gameType >= kGameTypeNancy3) {
|
||||
stream.skip(2);
|
||||
}
|
||||
|
||||
_blitDescriptions.resize(numFrames);
|
||||
for (uint i = 0; i < numFrames; ++i) {
|
||||
if (gameType <= kGameTypeNancy2) {
|
||||
_blitDescriptions[i].readData(stream);
|
||||
} else {
|
||||
_blitDescriptions[i].frameID = i;
|
||||
readRect(stream, _blitDescriptions[i].src);
|
||||
readRect(stream, _blitDescriptions[i].dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShowInventoryItem::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun: {
|
||||
int newFrame = -1;
|
||||
|
||||
for (uint i = 0; i < _blitDescriptions.size(); ++i) {
|
||||
if (_blitDescriptions[i].frameID == NancySceneState.getSceneInfo().frameID) {
|
||||
newFrame = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newFrame != _drawnFrameID) {
|
||||
_drawnFrameID = newFrame;
|
||||
|
||||
if (newFrame != -1) {
|
||||
_hasHotspot = true;
|
||||
_hotspot = _blitDescriptions[newFrame].dest;
|
||||
_drawSurface.create(_fullSurface, _blitDescriptions[newFrame].src);
|
||||
_screenPosition = _blitDescriptions[newFrame].dest;
|
||||
setVisible(true);
|
||||
} else {
|
||||
_hasHotspot = false;
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kActionTrigger:
|
||||
g_nancy->_sound->playSound("BUOK");
|
||||
NancySceneState.addItemToInventory(_objectID);
|
||||
setVisible(false);
|
||||
_hasHotspot = false;
|
||||
finishExecution();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void InventorySoundOverride::readData(Common::SeekableReadStream &stream) {
|
||||
_command = stream.readByte();
|
||||
_itemID = stream.readUint16LE();
|
||||
stream.skip(2);
|
||||
char buf[61];
|
||||
stream.read(buf, 60);
|
||||
buf[60] = '\0';
|
||||
_caption = buf;
|
||||
_sound.readNormal(stream);
|
||||
}
|
||||
|
||||
void InventorySoundOverride::execute() {
|
||||
NancySceneState.installInventorySoundOverride(_command, _sound, _caption, _itemID);
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void EnableDisableInventory::readData(Common::SeekableReadStream &stream) {
|
||||
_itemID = stream.readUint16LE();
|
||||
bool disabled = stream.readUint16LE();
|
||||
bool playSound = stream.readUint16LE();
|
||||
|
||||
if (disabled) {
|
||||
++_disabledState;
|
||||
if (playSound) {
|
||||
++_disabledState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EnableDisableInventory::execute() {
|
||||
NancySceneState.setItemDisabledState(_itemID, _disabledState);
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void PopInvViewPriorScene::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void PopInvViewPriorScene::execute() {
|
||||
NancySceneState.popScene(true);
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void GoInvViewScene::readData(Common::SeekableReadStream &stream) {
|
||||
_itemID = stream.readUint16LE();
|
||||
_addToInventory = stream.readUint16LE();
|
||||
}
|
||||
|
||||
void GoInvViewScene::execute() {
|
||||
auto *inv = GetEngineData(INV);
|
||||
assert(inv);
|
||||
|
||||
const INV::ItemDescription &item = inv->itemDescriptions[_itemID];
|
||||
byte disabled = NancySceneState.getItemDisabledState(_itemID);
|
||||
|
||||
if (!disabled && item.keepItem == kInvItemNewSceneView) {
|
||||
if (_addToInventory || NancySceneState.hasItem(_itemID)) {
|
||||
NancySceneState.pushScene(_itemID);
|
||||
} else {
|
||||
// Do not add the item to the inventory, only go to its scene
|
||||
NancySceneState.pushScene();
|
||||
}
|
||||
|
||||
SceneChangeDescription sceneChange;
|
||||
sceneChange.sceneID = item.sceneID;
|
||||
sceneChange.continueSceneSound = item.sceneSoundFlag;
|
||||
NancySceneState.changeScene(sceneChange);
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
138
engines/nancy/action/inventoryrecords.h
Normal file
138
engines/nancy/action/inventoryrecords.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_INVENTORYRECORDS_H
|
||||
#define NANCY_ACTION_INVENTORYRECORDS_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Simply adds an item to the player's inventory.
|
||||
class AddInventoryNoHS : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
uint16 _itemID = 0;
|
||||
bool _setCursor = false;
|
||||
bool _forceCursor = false;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "AddInventoryNoHS"; }
|
||||
};
|
||||
|
||||
// Simply removes an item from the player's inventory.
|
||||
class RemoveInventoryNoHS : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
uint _itemID;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "RemoveInventoryNoHS"; }
|
||||
};
|
||||
|
||||
// Displays a static image inside the viewport. The static image corresponds to an
|
||||
// inventory item, and is only displayed if the item is not in the player's possession.
|
||||
// On click, it hides the image and adds the item to the inventory.
|
||||
class ShowInventoryItem : public RenderActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
ShowInventoryItem() : RenderActionRecord(9) {}
|
||||
virtual ~ShowInventoryItem() { _fullSurface.free(); }
|
||||
|
||||
void init() override;
|
||||
|
||||
uint16 _objectID = 0;
|
||||
Common::Path _imageName;
|
||||
Common::Array<FrameBlitDescription> _blitDescriptions;
|
||||
|
||||
int16 _drawnFrameID = -1;
|
||||
Graphics::ManagedSurface _fullSurface;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override { return "ShowInventoryItem"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
// When clicking an ActionRecord hotspot with a kItem dependency, the engine
|
||||
// checks if the required item is currently being held; when it isn't, it plays
|
||||
// a specific sound to inform the player they need some item. This AR changes that
|
||||
// sound and its related caption (or stops it from playing entirely).
|
||||
class InventorySoundOverride : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
byte _command = 0;
|
||||
uint16 _itemID = 0;
|
||||
SoundDescription _sound;
|
||||
Common::String _caption;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "InventorySoundOverride"; }
|
||||
};
|
||||
|
||||
// Temporarily disable (or re-enable) clicking on a specific item in the inventory box
|
||||
class EnableDisableInventory : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
uint16 _itemID = 0;
|
||||
byte _disabledState = 0;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "EnableDisableInventory"; }
|
||||
};
|
||||
|
||||
// Pops the scene and item that get pushed when a player clicks a kInvItemNewSceneView item
|
||||
class PopInvViewPriorScene : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PopInvViewPriorScene"; }
|
||||
};
|
||||
|
||||
class GoInvViewScene : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "GoInvViewScene"; }
|
||||
|
||||
uint16 _itemID = 0;
|
||||
bool _addToInventory = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_INVENTORYRECORDS_H
|
||||
345
engines/nancy/action/miscrecords.cpp
Normal file
345
engines/nancy/action/miscrecords.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
/* 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/sound.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/miscrecords.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "common/events.h"
|
||||
#include "common/config-manager.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void PaletteThisScene::readData(Common::SeekableReadStream &stream) {
|
||||
_paletteID = stream.readByte();
|
||||
_unknownEnum = stream.readByte();
|
||||
_paletteStart = stream.readUint16LE();
|
||||
_paletteSize = stream.readUint16LE();
|
||||
}
|
||||
|
||||
void PaletteThisScene::execute() {
|
||||
NancySceneState.getSceneInfo().paletteID = _paletteID;
|
||||
const State::Scene::SceneSummary &ssum = NancySceneState.getSceneSummary();
|
||||
|
||||
if (_unknownEnum > 1 && _unknownEnum < 3) {
|
||||
// Not sure what the difference is between the 3 types
|
||||
NancySceneState.getViewport().setPalette(ssum.palettes[_paletteID], _paletteStart, _paletteSize);
|
||||
} else {
|
||||
NancySceneState.getViewport().setPalette(ssum.palettes[_paletteID]);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
|
||||
void PaletteNextScene::readData(Common::SeekableReadStream &stream) {
|
||||
// Structure is the same as PaletteThisScene, but the original engine only uses the palette ID
|
||||
_paletteID = stream.readByte();
|
||||
stream.skip(5);
|
||||
}
|
||||
|
||||
void PaletteNextScene::execute() {
|
||||
NancySceneState.getNextSceneInfo().paletteID = _paletteID;
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void LightningOn::readData(Common::SeekableReadStream &stream) {
|
||||
_distance = stream.readSint16LE();
|
||||
_pulseTime = stream.readUint16LE();
|
||||
_rgbPercent = stream.readSint16LE();
|
||||
stream.skip(4);
|
||||
}
|
||||
|
||||
void SpecialEffect::readData(Common::SeekableReadStream &stream) {
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy6) {
|
||||
_type = stream.readByte();
|
||||
_fadeToBlackTime = stream.readUint16LE();
|
||||
_frameTime = stream.readUint16LE();
|
||||
} else {
|
||||
_type = stream.readByte();
|
||||
_totalTime = stream.readUint16LE();
|
||||
_fadeToBlackTime = stream.readUint16LE();
|
||||
readRect(stream, _rect);
|
||||
}
|
||||
}
|
||||
|
||||
void SpecialEffect::execute() {
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy6) {
|
||||
NancySceneState.specialEffect(_type, _fadeToBlackTime, _frameTime);
|
||||
} else {
|
||||
NancySceneState.specialEffect(_type, _totalTime, _fadeToBlackTime, _rect);
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void LightningOn::execute() {
|
||||
NancySceneState.beginLightning(_distance, _pulseTime, _rgbPercent);
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void TextBoxWrite::readData(Common::SeekableReadStream &stream) {
|
||||
int16 size = stream.readSint16LE();
|
||||
|
||||
if (size > 10000) {
|
||||
error("Action Record atTextboxWrite has too many text box chars: %d", size);
|
||||
}
|
||||
|
||||
if (size == -1) {
|
||||
Common::String stringID;
|
||||
readFilename(stream, stringID);
|
||||
|
||||
const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
|
||||
assert(autotext);
|
||||
|
||||
_text = autotext->texts[stringID];
|
||||
} else {
|
||||
char *buf = new char[size];
|
||||
stream.read(buf, size);
|
||||
buf[size - 1] = '\0';
|
||||
|
||||
assembleTextLine(buf, _text, size);
|
||||
|
||||
delete[] buf;
|
||||
}
|
||||
}
|
||||
|
||||
void TextBoxWrite::execute() {
|
||||
auto &tb = NancySceneState.getTextbox();
|
||||
tb.clear();
|
||||
tb.addTextLine(_text);
|
||||
tb.setVisible(true);
|
||||
finishExecution();
|
||||
}
|
||||
|
||||
void TextboxClear::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void TextboxClear::execute() {
|
||||
NancySceneState.getTextbox().clear();
|
||||
finishExecution();
|
||||
}
|
||||
|
||||
void BumpPlayerClock::readData(Common::SeekableReadStream &stream) {
|
||||
_relative = stream.readByte();
|
||||
_hours = stream.readUint16LE();
|
||||
_minutes = stream.readUint16LE();
|
||||
}
|
||||
|
||||
void BumpPlayerClock::execute() {
|
||||
NancySceneState.setPlayerTime(_hours * 3600000 + _minutes * 60000, _relative);
|
||||
finishExecution();
|
||||
}
|
||||
|
||||
void SaveContinueGame::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void SaveContinueGame::execute() {
|
||||
g_nancy->secondChance();
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void TurnOffMainRendering::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void TurnOnMainRendering::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void ResetAndStartTimer::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void ResetAndStartTimer::execute() {
|
||||
NancySceneState.resetAndStartTimer();
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void StopTimer::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void StopTimer::execute() {
|
||||
NancySceneState.stopTimer();
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void GotoMenu::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void GotoMenu::execute() {
|
||||
//NancySceneState.setDestroyOnExit();
|
||||
g_nancy->setState(NancyState::kMainMenu);
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void LoseGame::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void LoseGame::execute() {
|
||||
g_nancy->_sound->stopAndUnloadSceneSpecificSounds();
|
||||
NancySceneState.setDestroyOnExit();
|
||||
|
||||
if (!ConfMan.hasKey("original_menus") || ConfMan.getBool("original_menus")) {
|
||||
g_nancy->setState(NancyState::kMainMenu);
|
||||
} else {
|
||||
Common::Event ev;
|
||||
ev.type = Common::EVENT_RETURN_TO_LAUNCHER;
|
||||
g_system->getEventManager()->pushEvent(ev);
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void PushScene::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void PushScene::execute() {
|
||||
NancySceneState.pushScene();
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void PopScene::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void PopScene::execute() {
|
||||
NancySceneState.popScene();
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void WinGame::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void WinGame::execute() {
|
||||
// Set ConfMan value that will stay persistent across future playthroughs.
|
||||
// Default value in original is StillWorkingOnIt, but we just don't set it instead.
|
||||
ConfMan.set("PlayerWonTheGame", "AcedTheGame", ConfMan.getActiveDomainName());
|
||||
ConfMan.flushToDisk();
|
||||
|
||||
g_nancy->_sound->stopAndUnloadSceneSpecificSounds();
|
||||
NancySceneState.setDestroyOnExit();
|
||||
g_nancy->setState(NancyState::kCredits, NancyState::kMainMenu);
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void HintSystem::readData(Common::SeekableReadStream &stream) {
|
||||
_characterID = stream.readByte();
|
||||
_genericSound.readNormal(stream);
|
||||
}
|
||||
|
||||
void HintSystem::execute() {
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
uint16 difficulty = NancySceneState.getDifficulty();
|
||||
selectHint();
|
||||
_genericSound.name = selectedHint->soundIDs[difficulty];
|
||||
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(g_nancy->getStaticData().hintTexts[selectedHint->textID * 3 + difficulty]);
|
||||
|
||||
g_nancy->_sound->loadSound(_genericSound);
|
||||
g_nancy->_sound->playSound(_genericSound);
|
||||
_state = kRun;
|
||||
break;
|
||||
}
|
||||
case kRun:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_genericSound)) {
|
||||
g_nancy->_sound->stopSound(_genericSound);
|
||||
_state = kActionTrigger;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// fall through
|
||||
case kActionTrigger:
|
||||
NancySceneState.useHint(_characterID, _hintID);
|
||||
NancySceneState.getTextbox().clear();
|
||||
|
||||
NancySceneState.changeScene(selectedHint->sceneChange);
|
||||
|
||||
_isDone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HintSystem::selectHint() {
|
||||
if (NancySceneState.getHintsRemaining() == 0) {
|
||||
selectedHint = &g_nancy->getStaticData().hints[_characterID][0];
|
||||
}
|
||||
|
||||
// Start from 1 since the first hint is always the "I give up" option
|
||||
for (uint i = 1; i < g_nancy->getStaticData().hints[_characterID].size(); ++i) {
|
||||
const auto &hint = g_nancy->getStaticData().hints[_characterID][i];
|
||||
|
||||
bool isSatisfied = true;
|
||||
|
||||
for (const auto &cond : hint.conditions) {
|
||||
switch (cond.type) {
|
||||
case (byte)StaticDataConditionType::kEvent :
|
||||
if (!NancySceneState.getEventFlag(cond.label, cond.flag)) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case (byte)StaticDataConditionType::kInventory :
|
||||
if (NancySceneState.hasItem(cond.label) != cond.flag) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case (byte)StaticDataConditionType::kDifficulty :
|
||||
if ( (NancySceneState.getDifficulty() != cond.label && cond.flag != 0) ||
|
||||
(NancySceneState.getDifficulty() == cond.label && cond.flag == 0) ) {
|
||||
isSatisfied = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isSatisfied) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSatisfied) {
|
||||
selectedHint = &hint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
256
engines/nancy/action/miscrecords.h
Normal file
256
engines/nancy/action/miscrecords.h
Normal file
@@ -0,0 +1,256 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_RECORDTYPES_H
|
||||
#define NANCY_ACTION_RECORDTYPES_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
class NancyEngine;
|
||||
|
||||
namespace Action {
|
||||
|
||||
// Changes the palette for the current scene's background. TVD only.
|
||||
class PaletteThisScene : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
byte _paletteID;
|
||||
byte _unknownEnum; // enum w values 1-3
|
||||
uint16 _paletteStart;
|
||||
uint16 _paletteSize;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PaletteThisScene"; }
|
||||
};
|
||||
|
||||
// Changes the palette for the next scene's background. TVD only.
|
||||
class PaletteNextScene : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
byte _paletteID;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PaletteNextScene"; }
|
||||
};
|
||||
|
||||
// Turns on (temporary) lightning effect. TVD Only.
|
||||
class LightningOn : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
int16 _distance;
|
||||
uint16 _pulseTime;
|
||||
int16 _rgbPercent;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "LightningOn"; }
|
||||
};
|
||||
|
||||
// Requests either a fade between two scenes, or a fade to black; fade executes when scene is changed. Nancy2 and up.
|
||||
class SpecialEffect : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
byte _type = 1;
|
||||
uint16 _fadeToBlackTime = 0;
|
||||
uint16 _frameTime = 0;
|
||||
uint16 _totalTime = 0;
|
||||
Common::Rect _rect;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SpecialEffect"; }
|
||||
};
|
||||
|
||||
// Adds a caption to the textbox.
|
||||
class TextBoxWrite : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
Common::String _text;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TextBoxWrite"; }
|
||||
};
|
||||
|
||||
// Clears the textbox. Used very rarely.
|
||||
class TextboxClear : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TextboxClear"; }
|
||||
};
|
||||
|
||||
// Changes the in-game time. Used prior to the introduction of SetPlayerClock.
|
||||
class BumpPlayerClock : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
byte _relative;
|
||||
uint16 _hours;
|
||||
uint16 _minutes;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "BumpPlayerClock"; }
|
||||
};
|
||||
|
||||
// Creates a Second Chance save.
|
||||
class SaveContinueGame : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SaveContinueGame"; }
|
||||
};
|
||||
|
||||
// Stops the screen from rendering. Our rendering system is different from the original engine's,
|
||||
// so we have no use for this.
|
||||
class TurnOffMainRendering : public Unimplemented {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TurnOffMainRendering"; }
|
||||
};
|
||||
|
||||
// Restarts screen rendering. Our rendering system is different from the original engine's,
|
||||
// so we have no use for this.
|
||||
class TurnOnMainRendering : public Unimplemented {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TurnOnMainRendering"; }
|
||||
};
|
||||
|
||||
// Starts the timer. Used in combination with Dependency types that check for
|
||||
// how much time has passed since the timer was started.
|
||||
class ResetAndStartTimer : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ResetAndStartTimer"; }
|
||||
};
|
||||
|
||||
// Stops the timer.
|
||||
class StopTimer : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "StopTimer"; }
|
||||
};
|
||||
|
||||
// Returns the player back to the main menu
|
||||
class GotoMenu : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "GotoMenu"; }
|
||||
};
|
||||
|
||||
// Stops the game and boots the player back to the Menu screen, while also making sure
|
||||
// they can't Continue. The devs took care to add Second Chance saves before every one
|
||||
// of these, to make sure the player can return to a state just before the dangerous part.
|
||||
class LoseGame : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "LoseGame"; }
|
||||
};
|
||||
|
||||
// Adds a scene to the "stack" (which is just a single value). Used in combination with PopScene.
|
||||
class PushScene : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PushScene"; }
|
||||
};
|
||||
|
||||
// Changes to the scene pushed onto the "stack". Scenes can be pushed via PushScene, or Conversation types.
|
||||
class PopScene : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PopScene"; }
|
||||
};
|
||||
|
||||
// Ends the game and boots the player to the Credits screen.
|
||||
// TODO: The original engine also sets a config option called PlayerWonTheGame,
|
||||
// which in turn is used to trigger whichever event flag marks that the player
|
||||
// has beat the game at least once, which in turn allows easter eggs to be shown.
|
||||
// We currently support none of this.
|
||||
class WinGame : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "WinGame"; }
|
||||
};
|
||||
|
||||
// Checks how many hints the player is allowed to get. If they are still allowed hints,
|
||||
// it selects an appropriate one and plays its sound/displays its caption in the Textbox.
|
||||
// The hint system was _only_ used in nancy1, since it's pretty limited and overly punishing.
|
||||
class HintSystem : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
byte _characterID; // 0x00
|
||||
SoundDescription _genericSound; // 0x01
|
||||
|
||||
const Hint *selectedHint;
|
||||
int16 _hintID;
|
||||
|
||||
void selectHint();
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "HintSystem"; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MISCRECORDS_H
|
||||
285
engines/nancy/action/navigationrecords.cpp
Normal file
285
engines/nancy/action/navigationrecords.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
/* 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/navigationrecords.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SceneChange::readData(Common::SeekableReadStream &stream) {
|
||||
_sceneChange.readData(stream);
|
||||
}
|
||||
|
||||
void SceneChange::execute() {
|
||||
NancySceneState.changeScene(_sceneChange);
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void HotMultiframeSceneChange::readData(Common::SeekableReadStream &stream) {
|
||||
SceneChange::readData(stream);
|
||||
uint16 numHotspots = stream.readUint16LE();
|
||||
|
||||
_hotspots.reserve(numHotspots);
|
||||
for (uint i = 0; i < numHotspots; ++i) {
|
||||
_hotspots.push_back(HotspotDescription());
|
||||
HotspotDescription &newDesc = _hotspots[i];
|
||||
newDesc.readData(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void HotMultiframeSceneChange::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:
|
||||
SceneChange::execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Hot1FrSceneChange::readData(Common::SeekableReadStream &stream) {
|
||||
if (!_isTerse) {
|
||||
SceneChange::readData(stream);
|
||||
_hotspotDesc.readData(stream);
|
||||
} else {
|
||||
_sceneChange.sceneID = stream.readUint16LE();
|
||||
_sceneChange.continueSceneSound = kContinueSceneSound;
|
||||
_sceneChange.listenerFrontVector.set(0, 0, 1);
|
||||
readRect(stream, _hotspotDesc.coords);
|
||||
}
|
||||
}
|
||||
|
||||
void Hot1FrSceneChange::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_hotspot = _hotspotDesc.coords;
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_hotspotDesc.frameID == NancySceneState.getSceneInfo().frameID) {
|
||||
_hasHotspot = true;
|
||||
} else {
|
||||
_hasHotspot = false;
|
||||
}
|
||||
break;
|
||||
case kActionTrigger:
|
||||
SceneChange::execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HotMultiframeMultisceneChange::readData(Common::SeekableReadStream &stream) {
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy2) {
|
||||
_onTrue._sceneChange.readData(stream);
|
||||
_onFalse._sceneChange.readData(stream);
|
||||
} else {
|
||||
_onTrue.readData(stream, true);
|
||||
_onFalse.readData(stream, true);
|
||||
}
|
||||
|
||||
_condType = stream.readByte();
|
||||
_conditionID = stream.readUint16LE();
|
||||
_conditionPayload = stream.readByte();
|
||||
uint numHotspots = stream.readUint16LE();
|
||||
|
||||
_hotspots.resize(numHotspots);
|
||||
|
||||
for (uint i = 0; i < numHotspots; ++i) {
|
||||
_hotspots[i].readData(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void HotMultiframeMultisceneChange::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
// set something to 1
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
_hasHotspot = false;
|
||||
|
||||
for (HotspotDescription &desc : _hotspots) {
|
||||
if (desc.frameID == NancySceneState.getSceneInfo().frameID) {
|
||||
_hotspot = desc.coords;
|
||||
_hasHotspot = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger: {
|
||||
bool conditionMet = false;
|
||||
switch (_condType) {
|
||||
case kFlagEvent:
|
||||
if (NancySceneState.getEventFlag(_conditionID, _conditionPayload)) {
|
||||
conditionMet = true;
|
||||
}
|
||||
break;
|
||||
case kFlagInventory:
|
||||
if (NancySceneState.hasItem(_conditionID) == _conditionPayload) {
|
||||
conditionMet = true;
|
||||
}
|
||||
break;
|
||||
case kFlagCursor:
|
||||
if (NancySceneState.getHeldItem() == _conditionPayload) {
|
||||
conditionMet = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (conditionMet) {
|
||||
_onTrue.execute();
|
||||
} else {
|
||||
_onFalse.execute();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HotMultiframeMultisceneCursorTypeSceneChange::readData(Common::SeekableReadStream &stream) {
|
||||
uint16 numScenes = stream.readUint16LE();
|
||||
_scenes.resize(numScenes);
|
||||
_cursorTypes.resize(numScenes);
|
||||
for (uint i = 0; i < numScenes; ++i) {
|
||||
_cursorTypes[i] = stream.readUint16LE();
|
||||
_scenes[i].readData(stream);
|
||||
}
|
||||
|
||||
stream.skip(2);
|
||||
_defaultScene.readData(stream);
|
||||
|
||||
uint16 numHotspots = stream.readUint16LE();
|
||||
_hotspots.resize(numHotspots);
|
||||
for (uint i = 0; i < numHotspots; ++i) {
|
||||
_hotspots[i].readData(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void HotMultiframeMultisceneCursorTypeSceneChange::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:
|
||||
for (uint i = 0; i < _cursorTypes.size(); ++i) {
|
||||
if (NancySceneState.getHeldItem() == _cursorTypes[i]) {
|
||||
NancySceneState.changeScene(_scenes[i]);
|
||||
|
||||
_isDone = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.changeScene(_defaultScene);
|
||||
_isDone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MapCall::readData(Common::SeekableReadStream &stream) {
|
||||
stream.skip(1);
|
||||
}
|
||||
|
||||
void MapCall::execute() {
|
||||
_execType = kRepeating;
|
||||
NancySceneState.requestStateChange(NancyState::kMap);
|
||||
finishExecution();
|
||||
}
|
||||
|
||||
void MapCallHot1Fr::readData(Common::SeekableReadStream &stream) {
|
||||
_hotspotDesc.readData(stream);
|
||||
}
|
||||
|
||||
void MapCallHot1Fr::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_hotspot = _hotspotDesc.coords;
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_hotspotDesc.frameID == NancySceneState.getSceneInfo().frameID) {
|
||||
_hasHotspot = true;
|
||||
}
|
||||
break;
|
||||
case kActionTrigger:
|
||||
MapCall::execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MapCallHotMultiframe::readData(Common::SeekableReadStream &stream) {
|
||||
uint16 numDescs = stream.readUint16LE();
|
||||
_hotspots.reserve(numDescs);
|
||||
for (uint i = 0; i < numDescs; ++i) {
|
||||
_hotspots.push_back(HotspotDescription());
|
||||
_hotspots[i].readData(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void MapCallHotMultiframe::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:
|
||||
MapCall::execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
200
engines/nancy/action/navigationrecords.h
Normal file
200
engines/nancy/action/navigationrecords.h
Normal file
@@ -0,0 +1,200 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_NAVIGATIONRECORDS_H
|
||||
#define NANCY_ACTION_NAVIGATIONRECORDS_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Simply changes the scene
|
||||
class SceneChange : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
SceneChangeDescription _sceneChange;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SceneChange"; }
|
||||
};
|
||||
|
||||
// Changes the scene when clicked. Hotspot can move along with scene background frame.
|
||||
// Nancy4 introduced several sub-types with a specific mouse cursor to show when
|
||||
// hovering; all of them are handled in this class as well.
|
||||
class HotMultiframeSceneChange : public SceneChange {
|
||||
public:
|
||||
HotMultiframeSceneChange(CursorManager::CursorType hoverCursor) : _hoverCursor(hoverCursor) {}
|
||||
virtual ~HotMultiframeSceneChange() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
CursorManager::CursorType getHoverCursor() const override { return _hoverCursor; }
|
||||
|
||||
Common::Array<HotspotDescription> _hotspots;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override {
|
||||
switch (_hoverCursor) {
|
||||
case CursorManager::kMoveForward:
|
||||
return "HotMultiframeForwardSceneChange";
|
||||
case CursorManager::kMoveUp:
|
||||
return "HotMultiframeUpSceneChange";
|
||||
case CursorManager::kMoveDown:
|
||||
return "HotMultiframeDownSceneChange";
|
||||
default:
|
||||
return "HotMultiframeSceneChange";
|
||||
}
|
||||
}
|
||||
|
||||
CursorManager::CursorType _hoverCursor;
|
||||
};
|
||||
|
||||
// Changes the scene when clicked; does _not_ move with scene background.
|
||||
// Nancy4 introduced several sub-types with a specific mouse cursor to show when
|
||||
// hovering; all of them are handled in this class as well.
|
||||
class Hot1FrSceneChange : public SceneChange {
|
||||
public:
|
||||
Hot1FrSceneChange(CursorManager::CursorType hoverCursor) : _hoverCursor(hoverCursor) {}
|
||||
virtual ~Hot1FrSceneChange() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
CursorManager::CursorType getHoverCursor() const override { return _hoverCursor; }
|
||||
|
||||
HotspotDescription _hotspotDesc;
|
||||
bool _isTerse = false;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override {
|
||||
if (_isTerse) {
|
||||
return "HotSceneChangeTerse";
|
||||
}
|
||||
|
||||
switch (_hoverCursor) {
|
||||
case CursorManager::kExit:
|
||||
return "Hot1FrExitSceneChange";
|
||||
case CursorManager::kMoveForward:
|
||||
return "Hot1FrForwardSceneChange";
|
||||
case CursorManager::kMoveBackward:
|
||||
return "Hot1FrBackSceneChange";
|
||||
case CursorManager::kMoveUp:
|
||||
return "Hot1FrUpSceneChange";
|
||||
case CursorManager::kMoveDown:
|
||||
return "Hot1FrDownSceneChange";
|
||||
case CursorManager::kMoveLeft:
|
||||
return "Hot1FrLeftSceneChange";
|
||||
case CursorManager::kMoveRight:
|
||||
return "Hot1FrRightSceneChange";
|
||||
default:
|
||||
return "Hot1FrSceneChange";
|
||||
}
|
||||
}
|
||||
|
||||
CursorManager::CursorType _hoverCursor;
|
||||
};
|
||||
|
||||
// Changes the scene when clicked. Hotspot can move along with scene background frame.
|
||||
// However, the scene it changes to can be one of two options, picked based on
|
||||
// a provided condition.
|
||||
class HotMultiframeMultisceneChange : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
SceneChangeWithFlag _onTrue;
|
||||
SceneChangeWithFlag _onFalse;
|
||||
byte _condType;
|
||||
uint16 _conditionID;
|
||||
byte _conditionPayload;
|
||||
Common::Array<HotspotDescription> _hotspots;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override { return "HotMultiframeMultisceneChange"; }
|
||||
};
|
||||
|
||||
// Changes the scene when clicked. Hotspot can move along with scene background frame.
|
||||
// However, the scene it changes to can be one of several options, picked based on
|
||||
// the item the player is currently holding.
|
||||
class HotMultiframeMultisceneCursorTypeSceneChange : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
Common::Array<SceneChangeDescription> _scenes;
|
||||
Common::Array<uint16> _cursorTypes;
|
||||
|
||||
SceneChangeDescription _defaultScene;
|
||||
Common::Array<HotspotDescription> _hotspots;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "HotMultiframeMultisceneCursorTypeSceneChange"; }
|
||||
};
|
||||
|
||||
// Simply switches to the Map state. TVD/nancy1 only.
|
||||
class MapCall : public ActionRecord {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
CursorManager::CursorType getHoverCursor() const override { return CursorManager::kExit; }
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MapCall"; }
|
||||
};
|
||||
|
||||
// Switches to the Map state when clicked; does _not_ move with background frame. TVD/nancy1 only.
|
||||
class MapCallHot1Fr : public MapCall {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
HotspotDescription _hotspotDesc;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override { return "MapCallHot1Fr"; }
|
||||
};
|
||||
|
||||
// Switches to the Map state when clicked. Hotspot can move along with scene background frame. TVD/nancy1 only.
|
||||
class MapCallHotMultiframe : public MapCall {
|
||||
public:
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
Common::Array<HotspotDescription> _hotspots;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override { return "MapCallHotMultiframe"; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_NAVIGATIONRECORDS_H
|
||||
441
engines/nancy/action/overlay.cpp
Normal file
441
engines/nancy/action/overlay.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
/* 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/sound.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/cursor.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
|
||||
#include "engines/nancy/action/overlay.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "common/serializer.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void Overlay::init() {
|
||||
// Autotext overlays need special handling when blitting
|
||||
if (_imageName.baseName().hasPrefix("USE_")) {
|
||||
_usesAutotext = true;
|
||||
}
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _fullSurface);
|
||||
|
||||
_currentFrame = _firstFrame;
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void Overlay::handleInput(NancyInput &input) {
|
||||
// For no apparent reason, from nancy3 on the original engine handles Overlay input as a special case,
|
||||
// rather than simply set the general hotspot inside the ActionRecord struct. Special cases
|
||||
// (a.k.a puzzle types) get handled before regular ActionRecords, which means an Overlay
|
||||
// must take precedence when handling the mouse. Thus, out ActionManager class first iterates
|
||||
// through all records and calls their handleInput() function just to make sure this special
|
||||
// case is handled. This fixes nancy3 scene 7081.
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy3) {
|
||||
if (_hasHotspot) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
|
||||
// Make sure nothing else gets triggered
|
||||
// This is nancy3 and up, since we actually want to trigger other records in nancy2 (e.g. scene 2541)
|
||||
input.eatMouseInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Overlay::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Serializer ser(&stream, nullptr);
|
||||
ser.setVersion(g_nancy->getGameType());
|
||||
|
||||
uint16 numSrcRects = 0;
|
||||
|
||||
readFilename(ser, _imageName);
|
||||
ser.skip(2); // VIDEO_STOP_RENDERING or VIDEO_CONTINUE_RENDERING
|
||||
ser.syncAsUint16LE(_transparency);
|
||||
ser.syncAsUint16LE(_hasSceneChange);
|
||||
ser.syncAsUint16LE(_enableHotspot, kGameTypeNancy2, kGameTypeNancy2);
|
||||
ser.syncAsUint16LE(_z, kGameTypeNancy2);
|
||||
ser.syncAsUint16LE(_overlayType, kGameTypeNancy2);
|
||||
ser.syncAsUint16LE(numSrcRects, kGameTypeNancy2);
|
||||
|
||||
ser.syncAsUint16LE(_playDirection);
|
||||
ser.syncAsUint16LE(_loop);
|
||||
ser.syncAsUint16LE(_firstFrame);
|
||||
ser.syncAsUint16LE(_loopFirstFrame);
|
||||
ser.syncAsUint16LE(_loopLastFrame);
|
||||
uint16 framesPerSec = stream.readUint16LE();
|
||||
|
||||
// Avoid divide by 0
|
||||
if (framesPerSec) {
|
||||
_frameTime = Common::Rational(1000, framesPerSec).toInt();
|
||||
}
|
||||
|
||||
ser.syncAsUint16LE(_z, kGameTypeNancy1, kGameTypeNancy1);
|
||||
|
||||
if (ser.getVersion() > kGameTypeNancy2) {
|
||||
if (_overlayType == kPlayOverlayStatic) {
|
||||
_enableHotspot = (_hasSceneChange == kPlayOverlaySceneChange) ? kPlayOverlayWithHotspot : kPlayOverlayNoHotspot;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isInterruptible) {
|
||||
ser.syncAsSint16LE(_interruptCondition.label);
|
||||
ser.syncAsUint16LE(_interruptCondition.flag);
|
||||
} else {
|
||||
_interruptCondition.label = kEvNoEvent;
|
||||
_interruptCondition.flag = g_nancy->_false;
|
||||
}
|
||||
|
||||
_sceneChange.readData(stream);
|
||||
_flagsOnTrigger.readData(stream);
|
||||
_sound.readNormal(stream);
|
||||
|
||||
uint numViewportFrames = stream.readUint16LE();
|
||||
|
||||
if (_overlayType == kPlayOverlayAnimated) {
|
||||
numSrcRects = _loopLastFrame - _firstFrame + 1;
|
||||
}
|
||||
|
||||
readRectArray(ser, _srcRects, numSrcRects);
|
||||
|
||||
_blitDescriptions.resize(numViewportFrames);
|
||||
for (auto &bm : _blitDescriptions) {
|
||||
bm.readData(stream, ser.getVersion() >= kGameTypeNancy2);
|
||||
}
|
||||
}
|
||||
|
||||
void Overlay::execute() {
|
||||
uint32 _currentFrameTime = g_nancy->getTotalPlayTime();
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_sound->loadSound(_sound);
|
||||
g_nancy->_sound->playSound(_sound);
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun: {
|
||||
// Check the timer to see if we need to draw the next animation frame
|
||||
if (_overlayType == kPlayOverlayAnimated && _nextFrameTime <= _currentFrameTime) {
|
||||
bool shouldTrigger = false;
|
||||
|
||||
// Check for interrupt flag
|
||||
if (NancySceneState.getEventFlag(_interruptCondition)) {
|
||||
shouldTrigger = true;
|
||||
}
|
||||
|
||||
// Wait until sound stops (if present)
|
||||
if (!g_nancy->_sound->isSoundPlaying(_sound)) {
|
||||
// Check if we're at the last frame
|
||||
if ((_currentFrame == _loopLastFrame) && (_playDirection == kPlayOverlayForward) && (_loop == kPlayOverlayOnce)) {
|
||||
shouldTrigger = true;
|
||||
} else if ((_currentFrame == _loopFirstFrame) && (_playDirection == kPlayOverlayReverse) && (_loop == kPlayOverlayOnce)) {
|
||||
shouldTrigger = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldTrigger) {
|
||||
_state = kActionTrigger;
|
||||
} else {
|
||||
// Check if we've moved the viewport
|
||||
uint16 newFrame = NancySceneState.getSceneInfo().frameID;
|
||||
|
||||
if (_currentViewportFrame != newFrame) {
|
||||
_currentViewportFrame = newFrame;
|
||||
|
||||
setVisible(false);
|
||||
_hasHotspot = false;
|
||||
|
||||
for (uint i = 0; i < _blitDescriptions.size(); ++i) {
|
||||
if (_currentViewportFrame == _blitDescriptions[i].frameID) {
|
||||
moveTo(_blitDescriptions[i].dest);
|
||||
setVisible(true);
|
||||
|
||||
if (_enableHotspot == kPlayOverlayWithHotspot) {
|
||||
_hotspot = _screenPosition;
|
||||
_hasHotspot = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16 frameDiff = 1;
|
||||
uint16 nextFrame = _currentFrame;
|
||||
|
||||
if (_nextFrameTime == 0) {
|
||||
_nextFrameTime = _currentFrameTime + _frameTime;
|
||||
} else {
|
||||
uint32 timeDiff = _currentFrameTime - _nextFrameTime;
|
||||
frameDiff = timeDiff / MAX<uint32>(_frameTime, 1); // Fix for nancy2 scene 1090, where _frameTime is 0
|
||||
_nextFrameTime += _frameTime * frameDiff;
|
||||
}
|
||||
|
||||
if (_playDirection == kPlayOverlayReverse) {
|
||||
if (nextFrame - frameDiff < _loopFirstFrame) {
|
||||
// We keep looping if sound is present (nancy1/2 only)
|
||||
if (_loop == kPlayOverlayLoop || (_sound.name != "NO SOUND" && g_nancy->getGameType() <= kGameTypeNancy2)) {
|
||||
nextFrame = _loopLastFrame - (frameDiff % (_loopLastFrame - _loopFirstFrame + 1));
|
||||
}
|
||||
} else {
|
||||
nextFrame -= frameDiff;
|
||||
}
|
||||
} else {
|
||||
if (nextFrame + frameDiff > _loopLastFrame) {
|
||||
if (_loop == kPlayOverlayLoop || (_sound.name != "NO SOUND" && g_nancy->getGameType() <= kGameTypeNancy2)) {
|
||||
nextFrame = _loopFirstFrame + (frameDiff % (_loopLastFrame - _loopFirstFrame + 1));
|
||||
}
|
||||
} else {
|
||||
nextFrame += frameDiff;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for:
|
||||
// - the arcade machine in nancy1 scene 833
|
||||
// - the fireplace in nancy2 scene 2491, where one of the rects is invalid.
|
||||
// - the ball thing in nancy2 scene 1562, where one of the rects is twice as tall as it should be
|
||||
// Assumes all rects in a single animation have the same dimensions
|
||||
Common::Rect srcRect = _srcRects[nextFrame];
|
||||
if (!srcRect.isValidRect() || srcRect.width() != _srcRects[0].width() || srcRect.height() != _srcRects[0].height()) {
|
||||
srcRect.setWidth(_srcRects[0].width());
|
||||
srcRect.setHeight(_srcRects[0].height());
|
||||
}
|
||||
|
||||
_drawSurface.create(_fullSurface, srcRect);
|
||||
setTransparent(_transparency == kPlayOverlayTransparent);
|
||||
|
||||
_currentFrame = nextFrame;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
} else {
|
||||
// Check if we've moved the viewport
|
||||
uint16 newFrame = NancySceneState.getSceneInfo().frameID;
|
||||
|
||||
if (_currentViewportFrame != newFrame) {
|
||||
_currentViewportFrame = newFrame;
|
||||
|
||||
setVisible(false);
|
||||
_hasHotspot = false;
|
||||
|
||||
// First, check if there's more than one blit description for the current viewport frame.
|
||||
// This happens in nancy7 scene 3600
|
||||
Common::Array<uint16> blitsForThisFrame;
|
||||
Common::Rect destRect;
|
||||
for (uint i = 0; i < _blitDescriptions.size(); ++i) {
|
||||
if (_currentViewportFrame == _blitDescriptions[i].frameID) {
|
||||
blitsForThisFrame.push_back(i);
|
||||
if (destRect.isEmpty()) {
|
||||
destRect = _blitDescriptions[i].dest;
|
||||
} else {
|
||||
destRect.extend(_blitDescriptions[i].dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_overlayType == kPlayOverlayStatic && blitsForThisFrame.size()) {
|
||||
moveTo(destRect);
|
||||
setVisible(true);
|
||||
|
||||
if (blitsForThisFrame.size() != 1) {
|
||||
_drawSurface.create(destRect.width(), destRect.height(), _fullSurface.format);
|
||||
setTransparent(true); // Force transparency. This shouldn't break anything. Hopefully.
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
for (uint i = 0; i < blitsForThisFrame.size(); ++i) {
|
||||
// In static mode every "animation" frame corresponds to a viewport frame
|
||||
// Static mode overlays use both the general source rects (_srcRects),
|
||||
// and the ones inside the blit description struct corresponding to the current scene background.
|
||||
|
||||
// BlitDescriptions contain the id of the source rect to actually use
|
||||
Common::Rect srcRect = _srcRects[_blitDescriptions[blitsForThisFrame[i]].staticRectID];
|
||||
Common::Rect staticBounds = _blitDescriptions[blitsForThisFrame[i]].src;
|
||||
|
||||
if (_usesAutotext) {
|
||||
// For autotext overlays, the srcRect is junk data
|
||||
srcRect = staticBounds;
|
||||
} else {
|
||||
// Lastly, the general source rect we just got may also be completely empty (nancy5 scenes 2056, 2057),
|
||||
// or have coordinates other than (0, 0) (nancy3 scene 3070, nancy5 scene 2000). Presumably,
|
||||
// the general source rect was used for blitting to an (optional) intermediate surface, while the ones
|
||||
// inside the blit description below were used for blitting from that intermediate surface to the screen.
|
||||
// We can achieve the same results by doung the calculations below
|
||||
srcRect.translate(staticBounds.left, staticBounds.top);
|
||||
|
||||
if (srcRect.isEmpty()) {
|
||||
srcRect.setWidth(staticBounds.width());
|
||||
srcRect.setHeight(staticBounds.height());
|
||||
} else {
|
||||
// Grab whichever dimensions are smaller. Fixes the book in nancy5 scene 3000
|
||||
srcRect.setWidth(MIN<int>(staticBounds.width(), srcRect.width()));
|
||||
srcRect.setHeight(MIN<int>(staticBounds.height(), srcRect.height()));
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the srcRect doesn't extend beyond the image.
|
||||
// This fixes nancy7 scene 4228
|
||||
srcRect.clip(_fullSurface.getBounds());
|
||||
|
||||
if (blitsForThisFrame.size() == 1) {
|
||||
_drawSurface.create(_fullSurface, srcRect);
|
||||
setTransparent(_transparency == kPlayOverlayTransparent);
|
||||
} else {
|
||||
Common::Rect d = _blitDescriptions[blitsForThisFrame[i]].dest;
|
||||
d.translate(-destRect.left, -destRect.top);
|
||||
_drawSurface.blitFrom(_fullSurface, srcRect, d);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy2) {
|
||||
// In nancy2, the presence of a hotspot relies on whether the Overlay has a scene change
|
||||
if (_enableHotspot == kPlayOverlayWithHotspot) {
|
||||
_hotspot = _screenPosition;
|
||||
_hasHotspot = true;
|
||||
}
|
||||
} else {
|
||||
// nancy3 added a per-frame flag for hotspots. This allows the overlay to be clickable
|
||||
// even without a scene change (useful for setting flags).
|
||||
if (_blitDescriptions[i].hasHotspot == kPlayOverlayWithHotspot) {
|
||||
_hotspot = _screenPosition;
|
||||
_hasHotspot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kActionTrigger:
|
||||
setVisible(false);
|
||||
g_nancy->_sound->stopSound(_sound);
|
||||
|
||||
_flagsOnTrigger.execute();
|
||||
if (_hasSceneChange == kPlayOverlaySceneChange) {
|
||||
NancySceneState.changeScene(_sceneChange);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Common::String Overlay::getRecordTypeName() const {
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy1) {
|
||||
if (_isInterruptible) {
|
||||
return "PlayIntStaticBitmapAnimation";
|
||||
} else {
|
||||
return "PlayStaticBitmapAnimation";
|
||||
}
|
||||
} else {
|
||||
return "Overlay";
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayStaticTerse::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
_transparency = stream.readUint16LE();
|
||||
_z = stream.readUint16LE();
|
||||
|
||||
Common::Rect dest, src;
|
||||
readRect(stream, dest);
|
||||
readRect(stream, src);
|
||||
|
||||
_srcRects.push_back(src);
|
||||
_blitDescriptions.resize(1);
|
||||
_blitDescriptions[0].src = Common::Rect(src.width(), src.height());
|
||||
_blitDescriptions[0].dest = dest;
|
||||
|
||||
_overlayType = kPlayOverlayStatic;
|
||||
}
|
||||
|
||||
void OverlayAnimTerse::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
stream.skip(2); // VIDEO_STOP_RENDERING, VIDEO_CONTINUE_RENDERING
|
||||
_transparency = stream.readUint16LE();
|
||||
_hasSceneChange = stream.readUint16LE();
|
||||
_z = stream.readUint16LE();
|
||||
_playDirection = stream.readUint16LE();
|
||||
_loop = stream.readUint16LE();
|
||||
|
||||
_sceneChange.sceneID = stream.readUint16LE();
|
||||
_sceneChange.continueSceneSound = kContinueSceneSound;
|
||||
_sceneChange.listenerFrontVector.set(0, 0, 1);
|
||||
_flagsOnTrigger.descs[0].label = stream.readSint16LE();
|
||||
_flagsOnTrigger.descs[0].flag = stream.readUint16LE();
|
||||
|
||||
_firstFrame = _loopFirstFrame = stream.readUint16LE();
|
||||
_loopLastFrame = stream.readUint16LE();
|
||||
|
||||
_blitDescriptions.resize(1);
|
||||
readRect(stream, _blitDescriptions[0].dest);
|
||||
|
||||
readRectArray(stream, _srcRects, _loopLastFrame - _loopFirstFrame + 1);
|
||||
|
||||
_overlayType = kPlayOverlayAnimated;
|
||||
_frameTime = Common::Rational(1000, 15).toInt(); // Always set to 15 fps
|
||||
}
|
||||
|
||||
void TableIndexOverlay::readData(Common::SeekableReadStream &stream) {
|
||||
_tableIndex = stream.readUint16LE();
|
||||
Overlay::readData(stream);
|
||||
}
|
||||
|
||||
void TableIndexOverlay::execute() {
|
||||
if (_state == kBegin) {
|
||||
Overlay::execute();
|
||||
}
|
||||
|
||||
TableData *playerTable = (TableData *)NancySceneState.getPuzzleData(TableData::getTag());
|
||||
assert(playerTable);
|
||||
auto *tabl = GetEngineData(TABL);
|
||||
assert(tabl);
|
||||
|
||||
if (_lastIndexVal != playerTable->singleValues[_tableIndex - 1]) {
|
||||
_lastIndexVal = playerTable->singleValues[_tableIndex - 1];
|
||||
_srcRects.clear();
|
||||
_srcRects.push_back(tabl->srcRects[_lastIndexVal - 1]);
|
||||
_currentViewportFrame = -1; // Force redraw
|
||||
}
|
||||
|
||||
if (_state != kBegin) {
|
||||
Overlay::execute();
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
135
engines/nancy/action/overlay.h
Normal file
135
engines/nancy/action/overlay.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_OVERLAY_H
|
||||
#define NANCY_ACTION_OVERLAY_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Places a static image or a looping animation on top of the background
|
||||
// Can move along with the scene's background frame, however:
|
||||
// - in animation mode, the animation is the same for every background frame
|
||||
// - in static mode, every background frame gets its own static image
|
||||
// Also supports:
|
||||
// - playing a sound;
|
||||
// - playing backwards;
|
||||
// - looping (non-looping animated overlays are very rare);
|
||||
// - getting interrupted by an event flag;
|
||||
// - changing the scene/setting flags when clicked/interrupted
|
||||
// Originally introduced in nancy1, where it was split into two different types:
|
||||
// PlayStaticBitmapAnimation and PlayIntStaticBitmapAnimation (the latter was interruptible)
|
||||
// In nancy2, the two got merged inside the newly-renamed Overlay;
|
||||
// that was also when static mode got introduced.
|
||||
class Overlay : public RenderActionRecord {
|
||||
public:
|
||||
Overlay(bool interruptible) : RenderActionRecord(7), _isInterruptible(interruptible), _usesAutotext(false) {}
|
||||
virtual ~Overlay() { _fullSurface.free(); }
|
||||
|
||||
void init() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _transparency = kPlayOverlayPlain;
|
||||
uint16 _hasSceneChange = kPlayOverlaySceneChange;
|
||||
uint16 _enableHotspot = kPlayOverlayNoHotspot;
|
||||
uint16 _overlayType = kPlayOverlayAnimated;
|
||||
uint16 _playDirection = kPlayOverlayForward;
|
||||
uint16 _loop = kPlayOverlayOnce;
|
||||
uint16 _firstFrame = 0;
|
||||
uint16 _loopFirstFrame = 0;
|
||||
uint16 _loopLastFrame = 0;
|
||||
uint32 _frameTime = 0;
|
||||
FlagDescription _interruptCondition;
|
||||
SceneChangeDescription _sceneChange;
|
||||
MultiEventFlagDescription _flagsOnTrigger;
|
||||
|
||||
Nancy::SoundDescription _sound;
|
||||
|
||||
// Describes a single frame in this animation
|
||||
Common::Array<Common::Rect> _srcRects;
|
||||
// Describes how the animation will be displayed on a single
|
||||
// frame of the viewport
|
||||
Common::Array<FrameBlitDescription> _blitDescriptions;
|
||||
|
||||
int16 _currentFrame = -1;
|
||||
int16 _currentViewportFrame = -1;
|
||||
uint32 _nextFrameTime = 0;
|
||||
bool _isInterruptible;
|
||||
bool _usesAutotext;
|
||||
|
||||
protected:
|
||||
bool canHaveHotspot() const override { return true; }
|
||||
Common::String getRecordTypeName() const override;
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Graphics::ManagedSurface _fullSurface;
|
||||
};
|
||||
|
||||
// Short version of a static overlay; assumes scene background doesn't move
|
||||
class OverlayStaticTerse : public Overlay {
|
||||
public:
|
||||
OverlayStaticTerse() : Overlay(true) {}
|
||||
virtual ~OverlayStaticTerse() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "OverlayStaticTerse"; }
|
||||
};
|
||||
|
||||
// Short version of an animated overlay; assumes scene background doesn't move
|
||||
class OverlayAnimTerse : public Overlay {
|
||||
public:
|
||||
OverlayAnimTerse() : Overlay(true) {}
|
||||
virtual ~OverlayAnimTerse() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "OverlayAnimTerse"; }
|
||||
};
|
||||
|
||||
class TableIndexOverlay : public Overlay {
|
||||
public:
|
||||
TableIndexOverlay() : Overlay(true) {}
|
||||
virtual ~TableIndexOverlay() {}
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TableIndexOverlay"; }
|
||||
|
||||
uint16 _tableIndex = 0;
|
||||
int16 _lastIndexVal = -1;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_OVERLAY_H
|
||||
99
engines/nancy/action/puzzle/angletosspuzzle.cpp
Normal file
99
engines/nancy/action/puzzle/angletosspuzzle.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/angletosspuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void AngleTossPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
void AngleTossPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - return to the winning screen
|
||||
warning("STUB - Nancy 8 Squid Toss game");
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 4465;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void AngleTossPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Path tmp;
|
||||
readFilename(stream, tmp);
|
||||
stream.skip(12); // TODO
|
||||
|
||||
for (uint i = 0; i < 22; ++i) {
|
||||
Common::Rect r;
|
||||
readRect(stream, r);
|
||||
|
||||
/*
|
||||
Common::String desc = Common::String::format("AngleTossPuzzle rect %d", i);
|
||||
debug("%s %d, %d, %d, %d", desc.c_str(), r.left, r.top, r.right, r.bottom);
|
||||
|
||||
Graphics::Surface *s = g_system->lockScreen();
|
||||
s->fillRect(r, 255);
|
||||
g_system->unlockScreen();
|
||||
g_system->updateScreen();
|
||||
g_system->delayMillis(1000);
|
||||
*/
|
||||
}
|
||||
|
||||
_powerSound.readNormal(stream);
|
||||
_squeakSound.readNormal(stream);
|
||||
_chainSound.readNormal(stream);
|
||||
|
||||
_throwSquidScene.readData(stream);
|
||||
stream.skip(7); // TODO
|
||||
_exitScene.readData(stream);
|
||||
|
||||
stream.skip(16); // TODO
|
||||
}
|
||||
|
||||
void AngleTossPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
58
engines/nancy/action/puzzle/angletosspuzzle.h
Normal file
58
engines/nancy/action/puzzle/angletosspuzzle.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ANGLETOSSPUZZLE_H
|
||||
#define NANCY_ACTION_ANGLETOSSPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Squid Toss mini-game in Nancy 8
|
||||
|
||||
class AngleTossPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
AngleTossPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~AngleTossPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "AngleTossPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
SoundDescription _powerSound;
|
||||
SoundDescription _squeakSound;
|
||||
SoundDescription _chainSound;
|
||||
|
||||
SceneChangeWithFlag _throwSquidScene;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ANGLETOSSPUZZLE_H
|
||||
65
engines/nancy/action/puzzle/arcadepuzzle.cpp
Normal file
65
engines/nancy/action/puzzle/arcadepuzzle.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/arcadepuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void ArcadePuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void ArcadePuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - return to the winning screen
|
||||
warning("STUB - Nancy 8 Barnacle Blast game");
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 4445;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void ArcadePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void ArcadePuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/arcadepuzzle.h
Normal file
51
engines/nancy/action/puzzle/arcadepuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ARCADEPUZZLE_H
|
||||
#define NANCY_ACTION_ARCADEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Barnacle Blast (Arcanoid) mini-game in Nancy 8
|
||||
|
||||
class ArcadePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
ArcadePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~ArcadePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "ArcadePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ARCADEPUZZLE_H
|
||||
334
engines/nancy/action/puzzle/assemblypuzzle.cpp
Normal file
334
engines/nancy/action/puzzle/assemblypuzzle.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/assemblypuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void AssemblyPuzzle::init() {
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Piece &piece = _pieces[i];
|
||||
piece.curRotation = piece.placed ? piece.correctRotation : 0;
|
||||
piece._drawSurface.create(_image, piece.srcRects[piece.curRotation]);
|
||||
piece.setVisible(true);
|
||||
piece.setTransparent(true);
|
||||
piece.moveTo(piece.placed ? piece.destRects[piece.curRotation] : piece.startRect);
|
||||
piece.setZ(_z + i + _pieces.size());
|
||||
}
|
||||
|
||||
rotateBase(true);
|
||||
rotateBase(false);
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::registerGraphics() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i].registerGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (AssemblyPuzzleData *)NancySceneState.getPuzzleData(AssemblyPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint16 numPieces = stream.readUint16LE();
|
||||
_height = stream.readUint16LE();
|
||||
|
||||
readRect(stream, _cwCursorDest);
|
||||
readRect(stream, _ccwCursorDest);
|
||||
|
||||
_pieces.resize(numPieces);
|
||||
for (uint i = 0; i < numPieces; ++i) {
|
||||
Piece &piece = _pieces[i];
|
||||
readRectArray(stream, piece.srcRects, 4);
|
||||
readRectArray(stream, piece.destRects, 4);
|
||||
|
||||
readRect(stream, piece.startRect);
|
||||
|
||||
piece.correctRotation = stream.readUint16LE();
|
||||
piece.layer = stream.readUint16LE();
|
||||
piece.placed = stream.readUint16LE();
|
||||
|
||||
if (_puzzleState->solvedPuzzle) {
|
||||
piece.placed = true;
|
||||
}
|
||||
}
|
||||
stream.skip((12 - numPieces) * 150);
|
||||
|
||||
_rotateSound.readNormal(stream);
|
||||
_pickUpSound.readNormal(stream);
|
||||
_placeDownSound.readNormal(stream);
|
||||
|
||||
_allowWrongPieceHotspot = stream.readUint16LE();
|
||||
|
||||
_wrongPieceSounds.resize(4);
|
||||
_wrongPieceTexts.resize(4);
|
||||
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_wrongPieceSounds[i].readNormal(stream);
|
||||
}
|
||||
|
||||
char buf[200];
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
stream.read(buf, 200);
|
||||
assembleTextLine(buf, _wrongPieceTexts[i], 200);
|
||||
}
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
stream.read(buf, 200);
|
||||
assembleTextLine(buf, _solveText, 200);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (AssemblyPuzzleData *)NancySceneState.getPuzzleData(AssemblyPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_rotateSound);
|
||||
g_nancy->_sound->loadSound(_pickUpSound);
|
||||
g_nancy->_sound->loadSound(_placeDownSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_layersAssembled != _height) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_solveText);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_completed = true;
|
||||
|
||||
_state = kActionTrigger;
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_completed) {
|
||||
_puzzleState->solvedPuzzle = true;
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state == kActionTrigger && _completed && g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
_completed = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_cwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_ccwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_pieces[i].startRect).contains(input.mousePos)) {
|
||||
if (_pickedUpPiece == -1 && _pieces[i].placed) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (_pickedUpPiece != -1) {
|
||||
_pieces[_pickedUpPiece].putDown();
|
||||
_pieces[_pickedUpPiece].moveTo(_pieces[_pickedUpPiece].startRect);
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != (int)i && !_pieces[i].placed) {
|
||||
// Clicked on another piece while holding, swap them
|
||||
_pickedUpPiece = i;
|
||||
_pieces[i].pickUp();
|
||||
g_nancy->_sound->playSound(_pickUpSound);
|
||||
|
||||
for (uint j = 1; j < _pieces.size(); ++j) {
|
||||
Piece &piece = _pieces[j];
|
||||
if (!piece.placed && piece.getZOrder() > _pieces[i].getZOrder()) {
|
||||
piece.setZ(piece.getZOrder() - 1);
|
||||
piece.registerGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
_pieces[i].setZ(_z + _pieces.size() * 2);
|
||||
_pieces[i].registerGraphics();
|
||||
} else {
|
||||
// Clicked the dest of the picked up piece, or an already placed one; simply put it down
|
||||
_pickedUpPiece = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != -1) {
|
||||
// Piece picked up
|
||||
Piece &pickedUpPiece = _pieces[_pickedUpPiece];
|
||||
pickedUpPiece.handleInput(input);
|
||||
|
||||
bool isWrong = _curRotation != pickedUpPiece.correctRotation;
|
||||
bool otherIsPlaced = false;
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (_pieces[i].placed && _pieces[i].curRotation == 0 && _pieces[i].layer - 1 == (int)_layersAssembled) {
|
||||
otherIsPlaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!otherIsPlaced && (!isWrong || (_allowWrongPieceHotspot && _layersAssembled + 1 == pickedUpPiece.layer))) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(pickedUpPiece.destRects[0]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (!isWrong) {
|
||||
pickedUpPiece.putDown();
|
||||
pickedUpPiece.moveTo(pickedUpPiece.destRects[0]);
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
pickedUpPiece.placed = true;
|
||||
_pickedUpPiece = -1;
|
||||
|
||||
// Check for finished layer
|
||||
uint placedOnLayer = 0;
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (_pieces[i].layer == _layersAssembled + 1 && _pieces[i].placed) {
|
||||
++placedOnLayer;
|
||||
}
|
||||
}
|
||||
|
||||
if (placedOnLayer == 4) {
|
||||
++_layersAssembled;
|
||||
}
|
||||
} else if (_allowWrongPieceHotspot) {
|
||||
// Wrong place, play a sound
|
||||
g_nancy->_sound->loadSound(_wrongPieceSounds[_curRotation]);
|
||||
g_nancy->_sound->playSound(_wrongPieceSounds[_curRotation]);
|
||||
if (!_wrongPieceTexts[_curRotation].empty()) {
|
||||
NancySceneState.getTextbox().addTextLine(_wrongPieceTexts[_curRotation], 4000); // check
|
||||
}
|
||||
|
||||
// and put down the piece
|
||||
pickedUpPiece.putDown();
|
||||
pickedUpPiece.moveTo(pickedUpPiece.startRect);
|
||||
_pickedUpPiece = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssemblyPuzzle::rotateBase(bool ccw) {
|
||||
// _curRotation moves in the opposite direction to pieces' rotations
|
||||
_curRotation += ccw ? 1 : -1;
|
||||
if (_curRotation < 0) {
|
||||
_curRotation = 3;
|
||||
} else if (_curRotation > 3) {
|
||||
_curRotation = 0;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Piece &piece = _pieces[i];
|
||||
if (piece.placed) {
|
||||
piece.curRotation += ccw ? -1 : 1;
|
||||
if (piece.curRotation < 0) {
|
||||
piece.curRotation = 3;
|
||||
} else if (piece.curRotation > 3) {
|
||||
piece.curRotation = 0;
|
||||
}
|
||||
|
||||
uint base = 0;
|
||||
if (piece.curRotation == 0) {
|
||||
base = 2;
|
||||
} else if (piece.curRotation == 1 || piece.curRotation == 3) {
|
||||
base = 1;
|
||||
}
|
||||
|
||||
piece.setZ(_z + base + 4 * (piece.layer - 1));
|
||||
piece.registerGraphics();
|
||||
|
||||
piece.moveTo(piece.destRects[piece.curRotation]);
|
||||
piece._drawSurface.create(_image, piece.srcRects[piece.curRotation]);
|
||||
piece.setTransparent(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
104
engines/nancy/action/puzzle/assemblypuzzle.h
Normal file
104
engines/nancy/action/puzzle/assemblypuzzle.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ASSEMBLYPUZZLE_H
|
||||
#define NANCY_ACTION_ASSEMBLYPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct AssemblyPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
// Minigame where the player is provided with the broken pieces of something
|
||||
// (a piece of pottery in nancy6), and has to assemble it.
|
||||
class AssemblyPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
AssemblyPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~AssemblyPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "AssemblyPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void rotateBase(bool ccw);
|
||||
|
||||
struct Piece : Misc::MouseFollowObject {
|
||||
Common::Array<Common::Rect> srcRects;
|
||||
Common::Array<Common::Rect> destRects;
|
||||
|
||||
Common::Rect startRect;
|
||||
uint16 correctRotation = 0;
|
||||
uint16 layer = 0;
|
||||
bool placed = false;
|
||||
|
||||
int curRotation = 0;
|
||||
};
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _height = 0;
|
||||
|
||||
Common::Rect _cwCursorDest;
|
||||
Common::Rect _ccwCursorDest;
|
||||
|
||||
Common::Array<Piece> _pieces;
|
||||
|
||||
SoundDescription _rotateSound;
|
||||
SoundDescription _pickUpSound;
|
||||
SoundDescription _placeDownSound;
|
||||
|
||||
bool _allowWrongPieceHotspot = false;
|
||||
|
||||
Common::Array<SoundDescription> _wrongPieceSounds;
|
||||
Common::Array<Common::String> _wrongPieceTexts;
|
||||
|
||||
SceneChangeWithFlag _solveScene; // has 9999 in nancy6, so the puzzle doesn't auto-exit
|
||||
SoundDescription _solveSound;
|
||||
Common::String _solveText;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int _pickedUpPiece = -1;
|
||||
int _curRotation = 0;
|
||||
uint _layersAssembled = 0;
|
||||
bool _completed = false;
|
||||
|
||||
AssemblyPuzzleData *_puzzleState = nullptr;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ASSEMBLYPUZZLE_H
|
||||
299
engines/nancy/action/puzzle/bballpuzzle.cpp
Normal file
299
engines/nancy/action/puzzle/bballpuzzle.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/bballpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void BBallPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
// Set up flags
|
||||
if (NancySceneState.getEventFlag(_goodShootFlag, g_nancy->_true)) {
|
||||
// Last shot entered the hoop
|
||||
for (uint i = 0; i < _playerPositionFlags.size(); ++i) {
|
||||
if (NancySceneState.getEventFlag(_playerPositionFlags[i], g_nancy->_true)) {
|
||||
_curPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Unset last position flag
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[_curPosition], g_nancy->_false);
|
||||
|
||||
if ((int)_curPosition == _positions - 1) {
|
||||
// Beat the game once, reset to initial
|
||||
_curPosition = 0;
|
||||
} else {
|
||||
// In the middle of the game, move to next position
|
||||
++_curPosition;
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[_curPosition], g_nancy->_true);
|
||||
} else {
|
||||
// Last shot did not enter the hoop, reset to initial position
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[0], g_nancy->_true);
|
||||
|
||||
for (uint i = 1; i < _playerPositionFlags.size(); ++i) {
|
||||
NancySceneState.setEventFlag(_playerPositionFlags[i], g_nancy->_false);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset shot type flags
|
||||
for (uint i = 0; i < _badShootFlags.size(); ++i) {
|
||||
NancySceneState.setEventFlag(_badShootFlags[i], g_nancy->_false);
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_goodShootFlag, g_nancy->_false);
|
||||
|
||||
// Draw the current player position
|
||||
if (_curPosition > 0) {
|
||||
_drawSurface.blitFrom(_image, _playerSrcs[_curPosition - 1], _playerDest);
|
||||
}
|
||||
}
|
||||
|
||||
void BBallPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_positions = stream.readUint16LE();
|
||||
_powers = stream.readUint16LE();
|
||||
_angles = stream.readUint16LE();
|
||||
|
||||
_correctVals.resize(_positions);
|
||||
for (uint i = 0; i < _positions; ++i) {
|
||||
_correctVals[i].x = stream.readUint16LE();
|
||||
_correctVals[i].y = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRect(stream, _shootButtonDest);
|
||||
readRect(stream, _minusButtonDest);
|
||||
readRect(stream, _plusButtonDest);
|
||||
|
||||
readRect(stream, _playerDest);
|
||||
readRect(stream, _powerDest);
|
||||
readRect(stream, _angleDest);
|
||||
|
||||
readRectArray(stream, _angleSliderHotspots, 3);
|
||||
|
||||
readRect(stream, _shootButtonSrc);
|
||||
readRect(stream, _minusButtonSrc);
|
||||
readRect(stream, _plusButtonSrc);
|
||||
|
||||
readRectArray(stream, _playerSrcs, 3);
|
||||
readRectArray(stream, _powerSrcs, 5);
|
||||
readRectArray(stream, _anglesSrcs, 2);
|
||||
|
||||
_shootSound.readNormal(stream);
|
||||
_minusSound.readNormal(stream);
|
||||
_plusSound.readNormal(stream);
|
||||
|
||||
_shootSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
|
||||
_badShootFlags.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_badShootFlags[i] = stream.readSint16LE();
|
||||
}
|
||||
|
||||
_goodShootFlag = stream.readSint16LE();
|
||||
|
||||
_playerPositionFlags.resize(_positions);
|
||||
for (uint i = 0; i < _positions; ++i) {
|
||||
_playerPositionFlags[i] = stream.readSint16LE();
|
||||
}
|
||||
|
||||
_winFlag = stream.readUint16LE();
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void BBallPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_plusSound);
|
||||
g_nancy->_sound->loadSound(_minusSound);
|
||||
g_nancy->_sound->loadSound(_shootSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_pressedButton) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_plusSound) || g_nancy->_sound->isSoundPlaying(_minusSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_pressedButton = false;
|
||||
_drawSurface.fillRect(_powerDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_plusButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_minusButtonDest, _drawSurface.getTransparentColor());
|
||||
|
||||
if (_curPower > 0) {
|
||||
_drawSurface.blitFrom(_image, _powerSrcs[_curPower - 1], _powerDest);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_pressedButton) {
|
||||
// Pressed the shoot button
|
||||
if (g_nancy->_sound->isSoundPlaying(_shootSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int16 flagToSet = -1;
|
||||
|
||||
if ((int)_curPower == _correctVals[_curPosition].x && (int)_curAngle == _correctVals[_curPosition].y) {
|
||||
// Selected correct values
|
||||
flagToSet = _goodShootFlag;
|
||||
|
||||
if ((int)_curPosition == _positions - 1) {
|
||||
// Last throw, mark puzzle as solved
|
||||
NancySceneState.setEventFlag(_winFlag, g_nancy->_true);
|
||||
}
|
||||
} else if (_curPower == 0) {
|
||||
// Low throw
|
||||
flagToSet = _badShootFlags[2];
|
||||
} else if ((int)_curPower >= _correctVals[_curPosition].x && (int)_curAngle <= _correctVals[_curPosition].y) {
|
||||
// High throw
|
||||
flagToSet = _badShootFlags[0];
|
||||
} else {
|
||||
// Mid throw
|
||||
flagToSet = _badShootFlags[1];
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(flagToSet, g_nancy->_true);
|
||||
NancySceneState.changeScene(_shootSceneChange);
|
||||
} else {
|
||||
// Exited the puzzle
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_plusSound);
|
||||
g_nancy->_sound->stopSound(_minusSound);
|
||||
g_nancy->_sound->stopSound(_shootSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void BBallPuzzle::handleInput(NancyInput &input) {
|
||||
Common::Point localMousePos = input.mousePos;
|
||||
Common::Rect vpPos = NancySceneState.getViewport().getScreenPosition();
|
||||
localMousePos -= { vpPos.left, vpPos.top };
|
||||
|
||||
if (_exitHotspot.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (!_pressedButton &&input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _angleSliderHotspots.size(); ++i) {
|
||||
if (_curAngle != i && _angleSliderHotspots[i].contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.fillRect(_angleDest, _drawSurface.getTransparentColor());
|
||||
|
||||
if (i > 0) {
|
||||
_drawSurface.blitFrom(_image, _anglesSrcs[i - 1], _angleDest);
|
||||
}
|
||||
|
||||
_curAngle = i;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_curPower > 0 && _minusButtonDest.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
--_curPower;
|
||||
_drawSurface.blitFrom(_image, _minusButtonSrc, _minusButtonDest);
|
||||
g_nancy->_sound->playSound(_minusSound);
|
||||
_pressedButton = true;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int)_curPower < _powers - 1 && _plusButtonDest.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_curPower;
|
||||
_drawSurface.blitFrom(_image, _plusButtonSrc, _plusButtonDest);
|
||||
g_nancy->_sound->playSound(_plusSound);
|
||||
_pressedButton = true;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_shootButtonDest.contains(localMousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_pressedButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _shootButtonSrc, _shootButtonDest);
|
||||
g_nancy->_sound->playSound(_shootSound);
|
||||
_pressedButton = true;
|
||||
_needsRedraw = true;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
98
engines/nancy/action/puzzle/bballpuzzle.h
Normal file
98
engines/nancy/action/puzzle/bballpuzzle.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_BBALLPUZZLE_H
|
||||
#define NANCY_ACTION_BBALLPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class BBallPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
BBallPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~BBallPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "BBallPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _positions = 0;
|
||||
uint16 _powers = 0;
|
||||
uint16 _angles = 0;
|
||||
|
||||
Common::Array<Common::Point> _correctVals;
|
||||
|
||||
Common::Rect _shootButtonDest;
|
||||
Common::Rect _minusButtonDest;
|
||||
Common::Rect _plusButtonDest;
|
||||
|
||||
Common::Rect _playerDest;
|
||||
Common::Rect _powerDest;
|
||||
Common::Rect _angleDest;
|
||||
Common::Array<Common::Rect> _angleSliderHotspots;
|
||||
|
||||
Common::Rect _shootButtonSrc;
|
||||
Common::Rect _minusButtonSrc;
|
||||
Common::Rect _plusButtonSrc;
|
||||
|
||||
Common::Array<Common::Rect> _playerSrcs;
|
||||
Common::Array<Common::Rect> _powerSrcs;
|
||||
Common::Array<Common::Rect> _anglesSrcs;
|
||||
|
||||
SoundDescription _shootSound;
|
||||
SoundDescription _minusSound;
|
||||
SoundDescription _plusSound;
|
||||
|
||||
SceneChangeDescription _shootSceneChange;
|
||||
|
||||
Common::Array<int16> _badShootFlags;
|
||||
int16 _goodShootFlag = 0;
|
||||
Common::Array<int16> _playerPositionFlags;
|
||||
|
||||
int16 _winFlag = 0;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
uint _curAngle = 0;
|
||||
uint _curPower = 0;
|
||||
uint _curPosition = 0;
|
||||
|
||||
bool _pressedButton = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_BBALLPUZZLE_H
|
||||
279
engines/nancy/action/puzzle/bombpuzzle.cpp
Normal file
279
engines/nancy/action/puzzle/bombpuzzle.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/bombpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void BombPuzzle::init() {
|
||||
_screenPosition = _displayBounds;
|
||||
for (Common::Rect &r : _wireDests) {
|
||||
_screenPosition.extend(r);
|
||||
}
|
||||
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
RenderActionRecord::init();
|
||||
}
|
||||
|
||||
void BombPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRectArray(stream, _wireSrcs, 4);
|
||||
readRectArray(stream, _wireDests, 4);
|
||||
readRectArray(stream, _digitSrcs, 10);
|
||||
readRectArray(stream, _digitDests, 4);
|
||||
readRect(stream, _colonSrc);
|
||||
readRect(stream, _colonDest);
|
||||
readRect(stream, _displayBounds);
|
||||
|
||||
_solveOrder.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_solveOrder[i] = stream.readByte();
|
||||
}
|
||||
|
||||
_snipSound.readNormal(stream);
|
||||
_noToolSound.readNormal(stream);
|
||||
_toolID = stream.readUint16LE();
|
||||
|
||||
_solveSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_failSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_failSound.readNormal(stream);
|
||||
|
||||
switch (NancySceneState.getDifficulty()) {
|
||||
case 0:
|
||||
_timerTotalTime = 30 * 1000;
|
||||
break;
|
||||
case 1:
|
||||
_timerTotalTime = 25 * 1000;
|
||||
break;
|
||||
case 2:
|
||||
_timerTotalTime = 20 * 1000;
|
||||
break;
|
||||
}
|
||||
|
||||
_nextBlinkTime = _timerTotalTime;
|
||||
_timerBlinkTime = 10 * 1000; // 10 seconds for all difficulties
|
||||
}
|
||||
|
||||
void BombPuzzle::updateGraphics() {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Time timeRemaining = NancySceneState.getTimerTime();
|
||||
|
||||
if (timeRemaining == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeRemaining > _timerTotalTime) {
|
||||
timeRemaining = 0;
|
||||
} else {
|
||||
timeRemaining = _timerTotalTime - timeRemaining;
|
||||
}
|
||||
|
||||
bool toggleBlink = false;
|
||||
|
||||
if (timeRemaining < _nextBlinkTime) {
|
||||
_nextBlinkTime = timeRemaining - 300; // hardcoded to 300 ms
|
||||
toggleBlink = timeRemaining < _timerBlinkTime;
|
||||
}
|
||||
|
||||
if (_lastDrawnTime == timeRemaining.getSeconds() && !toggleBlink) {
|
||||
// State is the same as last call, do not redraw
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect t = _displayBounds;
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
_lastDrawnTime = timeRemaining.getSeconds();
|
||||
|
||||
// Clear the display
|
||||
_drawSurface.fillRect(t, _drawSurface.getTransparentColor());
|
||||
|
||||
if (toggleBlink) {
|
||||
if (!_isBlinking) {
|
||||
// Only clear the display
|
||||
_isBlinking = true;
|
||||
_needsRedraw = true;
|
||||
return;
|
||||
} else {
|
||||
// Redraw the display
|
||||
_isBlinking = false;
|
||||
}
|
||||
} else {
|
||||
if (_isBlinking) {
|
||||
// Only clear the display
|
||||
_needsRedraw = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add 1 second to timer so it starts at 30/25/20 seconds
|
||||
timeRemaining += 1000;
|
||||
|
||||
// Draw 10s of minutes
|
||||
t = _digitDests[0];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getMinutes() / 10], t);
|
||||
|
||||
// Draw 1s of minutes
|
||||
t = _digitDests[1];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getMinutes() % 10], t);
|
||||
|
||||
// Draw 10s of seconds
|
||||
t = _digitDests[2];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getSeconds() / 10], t);
|
||||
|
||||
// Draw 1s of seconds
|
||||
t = _digitDests[3];
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[timeRemaining.getSeconds() % 10], t);
|
||||
|
||||
// Draw colon
|
||||
t = _colonDest;
|
||||
t.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _colonSrc, t);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void BombPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_snipSound);
|
||||
g_nancy->_sound->loadSound(_noToolSound);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
break;
|
||||
case kRun: {
|
||||
bool fail = false;
|
||||
|
||||
for (uint i = 0; i < _playerOrder.size(); ++i) {
|
||||
if (_playerOrder[i] != _solveOrder[i]) {
|
||||
fail = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
_failed = true;
|
||||
_state = kActionTrigger;
|
||||
g_nancy->_sound->loadSound(_failSound);
|
||||
g_nancy->_sound->playSound(_failSound);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playerOrder.size() == _solveOrder.size()) {
|
||||
_failed = false;
|
||||
_state = kActionTrigger;
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kActionTrigger:
|
||||
if (_failed) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_failSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_failSound);
|
||||
_failSceneChange.execute();
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
_solveSceneChange.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_snipSound);
|
||||
g_nancy->_sound->stopSound(_noToolSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void BombPuzzle::handleInput(NancyInput &input) {
|
||||
for (uint i = 0 ; i < _wireDests.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_wireDests[i]).contains(input.mousePos)) {
|
||||
for (byte j : _playerOrder) {
|
||||
if (i == j) {
|
||||
// Wire already snipped, do nothing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (NancySceneState.getHeldItem() == _toolID) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_snipSound)) {
|
||||
_playerOrder.push_back(i);
|
||||
g_nancy->_sound->playSound(_snipSound);
|
||||
Common::Rect dest = _wireDests[i];
|
||||
dest.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _wireSrcs[i], dest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_noToolSound);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
78
engines/nancy/action/puzzle/bombpuzzle.h
Normal file
78
engines/nancy/action/puzzle/bombpuzzle.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_BOMBPUZZLE_H
|
||||
#define NANCY_ACTION_BOMBPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class BombPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
BombPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~BombPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "BombPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Rect> _wireSrcs;
|
||||
Common::Array<Common::Rect> _wireDests;
|
||||
Common::Array<Common::Rect> _digitSrcs;
|
||||
Common::Array<Common::Rect> _digitDests;
|
||||
Common::Rect _colonSrc;
|
||||
Common::Rect _colonDest;
|
||||
Common::Rect _displayBounds;
|
||||
Common::Array<byte> _solveOrder;
|
||||
SoundDescription _snipSound;
|
||||
SoundDescription _noToolSound;
|
||||
uint16 _toolID = 0;
|
||||
SceneChangeWithFlag _solveSceneChange;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _failSceneChange;
|
||||
SoundDescription _failSound;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<byte> _playerOrder;
|
||||
|
||||
Time _timerTotalTime;
|
||||
Time _timerBlinkTime;
|
||||
Time _nextBlinkTime;
|
||||
bool _isBlinking = false;
|
||||
uint _lastDrawnTime = 0;
|
||||
|
||||
bool _failed = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_BOMBPUZZLE_H
|
||||
463
engines/nancy/action/puzzle/bulpuzzle.cpp
Normal file
463
engines/nancy/action/puzzle/bulpuzzle.cpp
Normal file
@@ -0,0 +1,463 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/bulpuzzle.h"
|
||||
|
||||
#include "common/random.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void BulPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
reset(false);
|
||||
|
||||
for (int i = 0; i < _numPieces - 1; ++i) {
|
||||
_drawSurface.blitFrom(_image, _playerBarracksSrc, _playerBarracksDests[i]);
|
||||
_drawSurface.blitFrom(_image, _enemyBarracksSrc, _enemyBarracksDests[i]);
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
}
|
||||
|
||||
void BulPuzzle::updateGraphics() {
|
||||
bool isPlayer = _turn / _numRolls == 0;
|
||||
|
||||
if (_currentAction == kCapture && g_nancy->getTotalPlayTime() > _nextMoveTime) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_playerCapturedSound) || g_nancy->_sound->isSoundPlaying(_enemyCapturedSound)) {
|
||||
return;
|
||||
} else {
|
||||
if (isPlayer) {
|
||||
--_enemyPieces;
|
||||
} else {
|
||||
--_playerPieces;
|
||||
}
|
||||
|
||||
if (_playerPieces && _enemyPieces) {
|
||||
reset(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_changeLight && !g_nancy->_sound->isSoundPlaying(_moveSound)) {
|
||||
if (_turn == 0) {
|
||||
_drawSurface.fillRect(_enemyLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
} else if (_turn == _numRolls) {
|
||||
_drawSurface.fillRect(_playerLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _enemyLightSrc, _enemyLightDest);
|
||||
}
|
||||
|
||||
if (_turn == 0 || _turn == _numRolls) {
|
||||
_drawSurface.blitFrom(_image, _passButtonDisabledSrc, _passButtonDest);
|
||||
} else {
|
||||
_drawSurface.fillRect(_passButtonDest, _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_changeLight = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_nextMoveTime && g_nancy->getTotalPlayTime() > _nextMoveTime) {
|
||||
// First, handle buttons
|
||||
if (_pushedButton) {
|
||||
switch (_currentAction) {
|
||||
case kRoll:
|
||||
_drawSurface.fillRect(_rollButtonDest, _drawSurface.getTransparentColor());
|
||||
|
||||
// Do the roll logic here since it's more convenient
|
||||
for (uint i = 0; i < _diceDestsPlayer.size(); ++i) {
|
||||
Common::Rect *dest = isPlayer ? &_diceDestsPlayer[i] : &_diceDestsEnemy[i];
|
||||
_drawSurface.fillRect(*dest, _drawSurface.getTransparentColor());
|
||||
bool black = g_nancy->_randomSource->getRandomBit();
|
||||
if (black) {
|
||||
// Black, add one movement
|
||||
_drawSurface.blitFrom(_image, _diceBlackSrcs[g_nancy->_randomSource->getRandomNumber(_diceBlackSrcs.size() - 1)], *dest);
|
||||
++_moveDiff;
|
||||
} else {
|
||||
// Non-black, no movement
|
||||
_drawSurface.blitFrom(_image, _diceCleanSrcs[g_nancy->_randomSource->getRandomNumber(_diceCleanSrcs.size() - 1)], *dest);
|
||||
}
|
||||
}
|
||||
|
||||
if (_moveDiff == 0) {
|
||||
_moveDiff = 5;
|
||||
}
|
||||
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 200;
|
||||
break;
|
||||
case kPass:
|
||||
_drawSurface.fillRect(_passButtonDest, _drawSurface.getTransparentColor());
|
||||
|
||||
if (isPlayer) {
|
||||
_drawSurface.fillRect(_playerLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _enemyLightSrc, _enemyLightDest);
|
||||
_turn = _numRolls;
|
||||
} else {
|
||||
_drawSurface.fillRect(_enemyLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
_turn = 0;
|
||||
}
|
||||
|
||||
_currentAction = kNone;
|
||||
_nextMoveTime = 0;
|
||||
break;
|
||||
case kReset:
|
||||
_drawSurface.fillRect(_resetButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_cellDests[_playerPos], _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_cellDests[_enemyPos], _drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < _playerJailDests.size(); ++i) {
|
||||
_drawSurface.fillRect(_playerJailDests[i], _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_enemyJailDests[i], _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_pushedButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(_rollSound) ||
|
||||
g_nancy->_sound->isSoundPlaying(_passSound) ||
|
||||
g_nancy->_sound->isSoundPlaying(_resetSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, handle the movement logic
|
||||
switch (_currentAction) {
|
||||
case kRoll:
|
||||
if (_moveDiff) {
|
||||
// Moving
|
||||
movePiece(isPlayer);
|
||||
--_moveDiff;
|
||||
|
||||
if (_moveDiff || _playerPos == _enemyPos) {
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 200; // hardcoded
|
||||
} else {
|
||||
// This was the last move, go to next turn
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_currentAction = kNone;
|
||||
_turn = _turn + 1 > 3 ? 0 : _turn + 1;
|
||||
_changeLight = true;
|
||||
}
|
||||
} else {
|
||||
// Capturing
|
||||
SoundDescription &sound = isPlayer ? _enemyCapturedSound : _playerCapturedSound;
|
||||
g_nancy->_sound->loadSound(sound);
|
||||
g_nancy->_sound->playSound(sound);
|
||||
_drawSurface.fillRect(_cellDests[_playerPos], _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, isPlayer ? _enemyCapturedSrc : _playerCapturedSrc, _cellDests[_playerPos]);
|
||||
_currentAction = kCapture;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 1000;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
case kPass:
|
||||
_currentAction = kNone;
|
||||
_turn = (_turn + 1 > _numRolls * 2) ? 0 : _turn + 1;
|
||||
|
||||
return;
|
||||
case kReset:
|
||||
reset(false);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BulPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numCells = stream.readUint16LE();
|
||||
_numPieces = stream.readUint16LE();
|
||||
_numRolls = stream.readUint16LE();
|
||||
_playerStart = stream.readUint16LE();
|
||||
_enemyStart = stream.readUint16LE();
|
||||
|
||||
readRectArray(stream, _diceDestsPlayer, 4);
|
||||
readRectArray(stream, _diceDestsEnemy, 4);
|
||||
|
||||
readRectArray(stream, _cellDests, _numCells, 15);
|
||||
|
||||
readRectArray(stream, _playerBarracksDests, 6);
|
||||
readRectArray(stream, _playerJailDests, 6);
|
||||
readRectArray(stream, _enemyBarracksDests, 6);
|
||||
readRectArray(stream, _enemyJailDests, 6);
|
||||
|
||||
readRect(stream, _rollButtonDest);
|
||||
readRect(stream, _passButtonDest);
|
||||
readRect(stream, _resetButtonDest);
|
||||
readRect(stream, _playerLightDest);
|
||||
readRect(stream, _enemyLightDest);
|
||||
|
||||
_diceBlackSrcs.resize(4);
|
||||
_diceCleanSrcs.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
readRect(stream, _diceCleanSrcs[i]);
|
||||
readRect(stream, _diceBlackSrcs[i]);
|
||||
}
|
||||
|
||||
readRect(stream, _playerSrc);
|
||||
readRect(stream, _enemySrc);
|
||||
readRect(stream, _enemyCapturedSrc);
|
||||
readRect(stream, _playerCapturedSrc);
|
||||
|
||||
readRect(stream, _playerBarracksSrc);
|
||||
readRect(stream, _enemyBarracksSrc);
|
||||
readRect(stream, _playerJailSrc);
|
||||
readRect(stream, _enemyJailSrc);
|
||||
|
||||
readRect(stream, _rollButtonSrc);
|
||||
readRect(stream, _passButtonSrc);
|
||||
readRect(stream, _resetButtonSrc);
|
||||
readRect(stream, _playerLightSrc);
|
||||
readRect(stream, _enemyLightSrc);
|
||||
readRect(stream, _passButtonDisabledSrc);
|
||||
|
||||
_moveSound.readNormal(stream);
|
||||
_enemyCapturedSound.readNormal(stream);
|
||||
_playerCapturedSound.readNormal(stream);
|
||||
_rollSound.readNormal(stream);
|
||||
_passSound.readNormal(stream);
|
||||
_resetSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
_loseSoundDelay = stream.readUint16LE();
|
||||
_loseSound.readNormal(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void BulPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_rollSound);
|
||||
g_nancy->_sound->loadSound(_resetSound);
|
||||
g_nancy->_sound->loadSound(_passSound);
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_playerPieces == 0) {
|
||||
_state = kActionTrigger;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + _loseSoundDelay * 1000;
|
||||
}
|
||||
|
||||
if (_enemyPieces == 0) {
|
||||
_playerWon = true;
|
||||
_state = kActionTrigger;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
}
|
||||
|
||||
if (_state == kRun) {
|
||||
break;
|
||||
}
|
||||
|
||||
// fall through
|
||||
case kActionTrigger:
|
||||
SoundDescription &sound = _playerWon ? _solveSound : _loseSound;
|
||||
|
||||
if (g_nancy->getTotalPlayTime() >= _nextMoveTime) {
|
||||
_nextMoveTime = 0;
|
||||
g_nancy->_sound->loadSound(sound);
|
||||
g_nancy->_sound->playSound(sound);
|
||||
}
|
||||
|
||||
if (_nextMoveTime == 0 && !g_nancy->_sound->isSoundPlaying(sound)) {
|
||||
if (_playerWon) {
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BulPuzzle::handleInput(NancyInput &input) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
_nextMoveTime = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pushedButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool canClick = _currentAction == kNone && !g_nancy->_sound->isSoundPlaying(_moveSound);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_rollButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _rollButtonSrc, _rollButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_rollSound);
|
||||
_currentAction = kRoll;
|
||||
_pushedButton = true;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_turn % _numRolls) && NancySceneState.getViewport().convertViewportToScreen(_passButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _passButtonSrc, _passButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_passSound);
|
||||
_currentAction = kPass;
|
||||
_pushedButton = true;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_resetButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _resetButtonSrc, _resetButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_resetSound);
|
||||
_currentAction = kReset;
|
||||
_pushedButton = true;
|
||||
_nextMoveTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BulPuzzle::movePiece(bool player) {
|
||||
int16 &piecePos = player ? _playerPos : _enemyPos;
|
||||
_drawSurface.fillRect(_cellDests[piecePos], _drawSurface.getTransparentColor());
|
||||
piecePos += player ? 1 : -1;
|
||||
|
||||
if (ABS<int16>(_playerPos - _enemyPos) == 1) {
|
||||
// Redraw other piece in case one piece goes behind the other's back
|
||||
_drawSurface.blitFrom(_image, player ? _enemySrc : _playerSrc, _cellDests[player ? _enemyPos : _playerPos]);
|
||||
}
|
||||
|
||||
if (piecePos < 0) {
|
||||
piecePos = _cellDests.size() - 1;
|
||||
} else if (piecePos > (int)_cellDests.size() - 1) {
|
||||
piecePos = 0;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, player ? _playerSrc : _enemySrc, _cellDests[piecePos]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void BulPuzzle::reset(bool capture) {
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
|
||||
// Reset dice
|
||||
for (uint i = 0; i < _diceDestsPlayer.size(); ++i) {
|
||||
_drawSurface.blitFrom(_image, _diceCleanSrcs[i], _diceDestsPlayer[i]);
|
||||
_drawSurface.blitFrom(_image, _diceCleanSrcs[i], _diceDestsEnemy[i]);
|
||||
}
|
||||
|
||||
if (!capture) {
|
||||
_playerPieces = _enemyPieces = _numPieces;
|
||||
}
|
||||
|
||||
// Reset player/enemy
|
||||
_playerPos = _playerStart - 1;
|
||||
_enemyPos = _enemyStart - 1;
|
||||
_drawSurface.blitFrom(_image, _playerSrc, _cellDests[_playerPos]);
|
||||
_drawSurface.blitFrom(_image, _enemySrc, _cellDests[_enemyPos]);
|
||||
|
||||
// Reset to player turn
|
||||
_turn = 0;
|
||||
_drawSurface.blitFrom(_image, _playerLightSrc, _playerLightDest);
|
||||
|
||||
// Draw jail and barracks
|
||||
for (int i = 0; i < _numPieces - 1; ++i) {
|
||||
if (i < _playerPieces - 1) {
|
||||
// Draw piece in barracks
|
||||
_drawSurface.blitFrom(_image, _playerBarracksSrc, _playerBarracksDests[i]);
|
||||
} else {
|
||||
// Draw piece in jail
|
||||
_drawSurface.blitFrom(_image, _playerJailSrc, _enemyJailDests[i - _playerPieces + 1]);
|
||||
}
|
||||
|
||||
if (i < _enemyPieces - 1) {
|
||||
// Draw piece in barracks
|
||||
_drawSurface.blitFrom(_image, _enemyBarracksSrc, _enemyBarracksDests[i]);
|
||||
} else {
|
||||
// Draw piece in jail
|
||||
_drawSurface.blitFrom(_image, _enemyJailSrc, _playerJailDests[i - _enemyPieces + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw disabled pass button
|
||||
_drawSurface.blitFrom(_image, _passButtonDisabledSrc, _passButtonDest);
|
||||
|
||||
_currentAction = kNone;
|
||||
_nextMoveTime = 0;
|
||||
_pushedButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
134
engines/nancy/action/puzzle/bulpuzzle.h
Normal file
134
engines/nancy/action/puzzle/bulpuzzle.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_BULPUZZLE_H
|
||||
#define NANCY_ACTION_BULPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// A puzzle based around a simplified version of the Mayan game Bul
|
||||
class BulPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
BulPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~BulPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
enum BulAction { kNone, kRoll, kPass, kReset, kCapture };
|
||||
|
||||
void movePiece(bool player);
|
||||
void reset(bool capture);
|
||||
|
||||
Common::String getRecordTypeName() const override { return "BulPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _numCells = 0;
|
||||
uint16 _numPieces = 0;
|
||||
uint16 _numRolls = 0;
|
||||
|
||||
uint16 _playerStart = 0;
|
||||
uint16 _enemyStart = 0;
|
||||
|
||||
Common::Array<Common::Rect> _diceDestsPlayer;
|
||||
Common::Array<Common::Rect> _diceDestsEnemy;
|
||||
|
||||
Common::Array<Common::Rect> _cellDests;
|
||||
|
||||
Common::Array<Common::Rect> _playerBarracksDests;
|
||||
Common::Array<Common::Rect> _playerJailDests;
|
||||
|
||||
Common::Array<Common::Rect> _enemyBarracksDests;
|
||||
Common::Array<Common::Rect> _enemyJailDests;
|
||||
|
||||
Common::Rect _rollButtonDest;
|
||||
Common::Rect _passButtonDest;
|
||||
Common::Rect _resetButtonDest;
|
||||
Common::Rect _playerLightDest;
|
||||
Common::Rect _enemyLightDest;
|
||||
|
||||
Common::Array<Common::Rect> _diceBlackSrcs;
|
||||
Common::Array<Common::Rect> _diceCleanSrcs;
|
||||
|
||||
Common::Rect _playerSrc;
|
||||
Common::Rect _enemySrc;
|
||||
Common::Rect _playerCapturedSrc;
|
||||
Common::Rect _enemyCapturedSrc;
|
||||
|
||||
Common::Rect _playerBarracksSrc;
|
||||
Common::Rect _enemyBarracksSrc;
|
||||
Common::Rect _playerJailSrc;
|
||||
Common::Rect _enemyJailSrc;
|
||||
|
||||
Common::Rect _rollButtonSrc;
|
||||
Common::Rect _passButtonSrc;
|
||||
Common::Rect _passButtonDisabledSrc;
|
||||
Common::Rect _resetButtonSrc;
|
||||
Common::Rect _playerLightSrc;
|
||||
Common::Rect _enemyLightSrc;
|
||||
|
||||
SoundDescription _moveSound;
|
||||
SoundDescription _playerCapturedSound;
|
||||
SoundDescription _enemyCapturedSound;
|
||||
SoundDescription _rollSound;
|
||||
SoundDescription _passSound;
|
||||
SoundDescription _resetSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene; // also when losing
|
||||
uint16 _loseSoundDelay = 0;
|
||||
SoundDescription _loseSound;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int16 _playerPos = 0;
|
||||
int16 _playerPieces = 0;
|
||||
int16 _enemyPos = 0;
|
||||
int16 _enemyPieces = 0;
|
||||
|
||||
uint16 _turn = 0;
|
||||
uint16 _moveDiff = 0;
|
||||
uint32 _nextMoveTime = 0;
|
||||
bool _pushedButton = false;
|
||||
bool _changeLight = false;
|
||||
BulAction _currentAction = kNone;
|
||||
|
||||
bool _playerWon = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_BULPUZZLE_H
|
||||
703
engines/nancy/action/puzzle/collisionpuzzle.cpp
Normal file
703
engines/nancy/action/puzzle/collisionpuzzle.cpp
Normal file
@@ -0,0 +1,703 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/collisionpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void CollisionPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
_pieces.resize(_pieceSrcs.size(), Piece());
|
||||
for (uint i = 0; i < _pieceSrcs.size(); ++i) {
|
||||
_pieces[i]._drawSurface.create(_image, _pieceSrcs[i]);
|
||||
Common::Rect pos = getScreenPosition(_startLocations[i]);
|
||||
if (_lineWidth == 6) {
|
||||
pos.translate(-1, 0); // Improvement
|
||||
}
|
||||
_pieces[i].moveTo(pos);
|
||||
_pieces[i]._gridPos = _startLocations[i];
|
||||
_pieces[i].setVisible(true);
|
||||
_pieces[i].setTransparent(true);
|
||||
}
|
||||
} else {
|
||||
for (uint y = 0; y < _grid.size(); ++y) {
|
||||
for (uint x = 0; x < _grid[0].size(); ++x) {
|
||||
if (_grid[y][x] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Piece newPiece;
|
||||
uint id = _grid[y][x];
|
||||
|
||||
switch (id) {
|
||||
case 1 :
|
||||
newPiece._w = 2;
|
||||
break;
|
||||
case 2 :
|
||||
newPiece._h = 2;
|
||||
break;
|
||||
case 3 :
|
||||
newPiece._w = 3;
|
||||
break;
|
||||
case 4 :
|
||||
newPiece._h = 3;
|
||||
break;
|
||||
case 5 :
|
||||
newPiece._w = 2;
|
||||
newPiece._h = 2;
|
||||
break;
|
||||
case 6 :
|
||||
newPiece._w = 2;
|
||||
break;
|
||||
default :
|
||||
continue;
|
||||
}
|
||||
|
||||
newPiece._drawSurface.create(_image, _pieceSrcs[id - 1]);
|
||||
Common::Rect pos = getScreenPosition(Common::Point(x, y));
|
||||
if (_lineWidth == 6) {
|
||||
pos.translate(-1, 0); // Improvement
|
||||
}
|
||||
pos.setWidth(newPiece._drawSurface.w);
|
||||
pos.setHeight(newPiece._drawSurface.h);
|
||||
newPiece.moveTo(pos);
|
||||
newPiece._gridPos = Common::Point(x, y);
|
||||
newPiece.setVisible(true);
|
||||
newPiece.setTransparent(true);
|
||||
|
||||
if (id == 6) {
|
||||
// The solve piece is pushed to the front
|
||||
_pieces.insert_at(0, newPiece);
|
||||
} else {
|
||||
_pieces.push_back(newPiece);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
drawGrid();
|
||||
}
|
||||
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void CollisionPuzzle::registerGraphics() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i].registerGraphics();
|
||||
}
|
||||
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void CollisionPuzzle::updateGraphics() {
|
||||
if (_state == kRun) {
|
||||
if (_timerSrcs.size()) {
|
||||
uint32 currentTime = g_nancy->getTotalPlayTime() - _puzzleStartTime;
|
||||
int graphicForTime = currentTime / ((_timerTime * 1000) / _timerSrcs.size());
|
||||
if (graphicForTime != _currentTimerGraphic) {
|
||||
_drawSurface.fillRect(_timerDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _timerSrcs[graphicForTime], _timerDest);
|
||||
_needsRedraw = true;
|
||||
_currentTimerGraphic = graphicForTime;
|
||||
NancySceneState.setEventFlag(_timerFlagIds[graphicForTime], g_nancy->_true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentlyAnimating != -1) {
|
||||
// Framerate-dependent animation. Should be fine since we limit the engine to ~60fps
|
||||
++_currentAnimFrame;
|
||||
bool horizontal = _lastPosition.x != _pieces[_currentlyAnimating]._gridPos.x;
|
||||
int diff = horizontal ?
|
||||
_lastPosition.x - _pieces[_currentlyAnimating]._gridPos.x :
|
||||
_lastPosition.y - _pieces[_currentlyAnimating]._gridPos.y;
|
||||
|
||||
int maxFrames = _framesPerMove * abs(diff);
|
||||
if (_currentAnimFrame > maxFrames) {
|
||||
if (_puzzleType == kCollision && _grid[_pieces[_currentlyAnimating]._gridPos.y][_pieces[_currentlyAnimating]._gridPos.x] == _currentlyAnimating + 1) {
|
||||
g_nancy->_sound->playSound(_homeSound);
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_wallHitSound);
|
||||
}
|
||||
|
||||
_currentlyAnimating = -1;
|
||||
_currentAnimFrame = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect destRect = getScreenPosition(_lastPosition);
|
||||
Common::Rect endPos = getScreenPosition(_pieces[_currentlyAnimating]._gridPos);
|
||||
|
||||
if (_lineWidth == 6) {
|
||||
destRect.translate(-1, 0); // Improvement
|
||||
endPos.translate(-1, 0); // Improvement
|
||||
}
|
||||
|
||||
Common::Point dest(destRect.left, destRect.top);
|
||||
if (horizontal) {
|
||||
dest.x = destRect.left + (endPos.left - dest.x) * _currentAnimFrame / maxFrames;
|
||||
} else {
|
||||
dest.y = destRect.top + (endPos.top - dest.y) * _currentAnimFrame / maxFrames;
|
||||
}
|
||||
|
||||
_pieces[_currentlyAnimating].moveTo(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollisionPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
uint16 numPieces = 0;
|
||||
|
||||
uint16 width = stream.readUint16LE();
|
||||
uint16 height = stream.readUint16LE();
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
numPieces = stream.readUint16LE();
|
||||
} else {
|
||||
_tileMoveExitPos.y = stream.readUint16LE();
|
||||
_tileMoveExitPos.x = stream.readUint16LE();
|
||||
_tileMoveExitSize = stream.readUint16LE();
|
||||
numPieces = 6;
|
||||
}
|
||||
|
||||
_grid.resize(height, Common::Array<uint16>(width));
|
||||
for (uint y = 0; y < height; ++y) {
|
||||
for (uint x = 0; x < width; ++x) {
|
||||
_grid[y][x] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((8 - width) * 2);
|
||||
}
|
||||
stream.skip((8 - height) * 8 * 2);
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
_startLocations.resize(numPieces);
|
||||
for (uint i = 0; i < numPieces; ++i) {
|
||||
_startLocations[i].x = stream.readUint16LE();
|
||||
_startLocations[i].y = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((5 - numPieces) * 4);
|
||||
|
||||
readRectArray(stream, _pieceSrcs, numPieces, 5);
|
||||
readRectArray(stream, _homeSrcs, numPieces, 5);
|
||||
|
||||
readRect(stream, _verticalWallSrc);
|
||||
readRect(stream, _horizontalWallSrc);
|
||||
readRect(stream, _blockSrc);
|
||||
} else {
|
||||
readRectArray(stream, _pieceSrcs, 6);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
_usesExitButton = stream.readByte();
|
||||
readRect(stream, _exitButtonSrc);
|
||||
readRect(stream, _exitButtonDest);
|
||||
}
|
||||
}
|
||||
|
||||
_gridPos.x = stream.readUint32LE();
|
||||
_gridPos.y = stream.readUint32LE();
|
||||
|
||||
_lineWidth = stream.readUint16LE();
|
||||
_framesPerMove = stream.readUint16LE();
|
||||
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy7) {
|
||||
stream.skip(3);
|
||||
} else if (_puzzleType == kTileMove) {
|
||||
uint16 numTimerGraphics = stream.readUint16LE();
|
||||
_timerTime = stream.readUint32LE();
|
||||
readRectArray(stream, _timerSrcs, numTimerGraphics, 10);
|
||||
_timerFlagIds.resize(numTimerGraphics);
|
||||
for (uint i = 0; i < numTimerGraphics; ++i) {
|
||||
_timerFlagIds[i] = stream.readSint16LE();
|
||||
}
|
||||
stream.skip((10 - numTimerGraphics) * 2);
|
||||
readRect(stream, _timerDest);
|
||||
}
|
||||
|
||||
_moveSound.readNormal(stream);
|
||||
if (_puzzleType == kCollision) {
|
||||
_homeSound.readNormal(stream);
|
||||
}
|
||||
_wallHitSound.readNormal(stream);
|
||||
if (_puzzleType == kTileMove && g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
_exitButtonSound.readNormal(stream);
|
||||
}
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void CollisionPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
g_nancy->_sound->loadSound(_wallHitSound);
|
||||
g_nancy->_sound->loadSound(_homeSound);
|
||||
NancySceneState.setNoHeldItem();
|
||||
_puzzleStartTime = g_nancy->getTotalPlayTime();
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_currentlyAnimating != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check timer
|
||||
if (_timerSrcs.size()) {
|
||||
if ((g_nancy->getTotalPlayTime() - _puzzleStartTime) > _timerTime * 1000) {
|
||||
_state = kActionTrigger;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
// Check if every tile is in its "home"
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
if (_grid[_pieces[i]._gridPos.y][_pieces[i]._gridPos.x] != i + 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check if either:
|
||||
// - the solve tile is over the exit or;
|
||||
// - the solve tile is outside the bounds of the grid (and is thus inside the exit)
|
||||
Common::Point pos = _pieces[0]._gridPos;
|
||||
Common::Rect posRect(pos.x, pos.y, pos.x + _pieces[0]._w, pos.y + _pieces[0]._h);
|
||||
Common::Rect gridRect(_grid.size(), _grid[0].size());
|
||||
if (!posRect.contains(_tileMoveExitPos) && gridRect.contains(pos)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_state = kActionTrigger;
|
||||
_solved = true;
|
||||
return;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (_solveSoundPlayTime != 0) {
|
||||
if (g_nancy->getTotalPlayTime() < _solveSoundPlayTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_solveSoundPlayTime = 0;
|
||||
return;
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
}
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_exitButtonSound)) {
|
||||
return;
|
||||
}
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_moveSound);
|
||||
g_nancy->_sound->stopSound(_wallHitSound);
|
||||
g_nancy->_sound->stopSound(_homeSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
Common::Point CollisionPuzzle::movePiece(uint pieceID, WallType direction) {
|
||||
Common::Point newPos = _pieces[pieceID]._gridPos;
|
||||
bool done = false;
|
||||
|
||||
uint preStopWallType = 0;
|
||||
uint postStopWallType = 0;
|
||||
int inc = 0;
|
||||
bool horizontal = false;
|
||||
|
||||
switch (direction) {
|
||||
case kWallLeft :
|
||||
preStopWallType = kWallRight;
|
||||
postStopWallType = kWallLeft;
|
||||
inc = -1;
|
||||
horizontal = true;
|
||||
|
||||
break;
|
||||
case kWallRight :
|
||||
preStopWallType = kWallLeft;
|
||||
postStopWallType = kWallRight;
|
||||
inc = 1;
|
||||
horizontal = true;
|
||||
|
||||
break;
|
||||
case kWallUp :
|
||||
preStopWallType = kWallDown;
|
||||
postStopWallType = kWallUp;
|
||||
inc = -1;
|
||||
horizontal = false;
|
||||
|
||||
break;
|
||||
case kWallDown :
|
||||
preStopWallType = kWallUp;
|
||||
postStopWallType = kWallDown;
|
||||
inc = 1;
|
||||
horizontal = false;
|
||||
|
||||
break;
|
||||
default:
|
||||
return { -1, -1 };
|
||||
}
|
||||
|
||||
// Set the last possible position to check before the piece would be out of bounds
|
||||
int lastPos = inc > 0 ? (horizontal ? (int)_grid[0].size() : (int)_grid.size()) : -1;
|
||||
if (lastPos != -1) {
|
||||
// For TileMove, ensure wider pieces won't clip out
|
||||
lastPos -= inc * ((horizontal ? _pieces[pieceID]._w : _pieces[pieceID]._h) - 1);
|
||||
}
|
||||
|
||||
for (int i = (horizontal ? newPos.x : newPos.y) + inc; (inc > 0 ? i < lastPos : i > lastPos); i += inc) {
|
||||
// First, check if other pieces would block
|
||||
Common::Point comparePos = newPos;
|
||||
if (horizontal) {
|
||||
comparePos.x = i;
|
||||
} else {
|
||||
comparePos.y = i;
|
||||
}
|
||||
|
||||
Common::Rect compareRect(comparePos.x, comparePos.y, comparePos.x + _pieces[pieceID]._w, comparePos.y + _pieces[pieceID]._h);
|
||||
|
||||
for (uint j = 0; j < _pieces.size(); ++j) {
|
||||
if (pieceID == j) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Common::Rect pieceBounds( _pieces[j]._gridPos.x,
|
||||
_pieces[j]._gridPos.y,
|
||||
_pieces[j]._gridPos.x + _pieces[j]._w,
|
||||
_pieces[j]._gridPos.y + _pieces[j]._h);
|
||||
if (pieceBounds.intersects(compareRect)) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (_puzzleType == kCollision) {
|
||||
// Next, check the grid for blocking walls
|
||||
uint16 evalVal = horizontal ? _grid[newPos.y][i] : _grid[i][newPos.x];
|
||||
if (evalVal == postStopWallType) {
|
||||
if (horizontal) {
|
||||
newPos.x = i;
|
||||
} else {
|
||||
newPos.y = i;
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (evalVal == preStopWallType || evalVal == kBlock) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (horizontal) {
|
||||
newPos.x = i;
|
||||
} else {
|
||||
newPos.y = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Move result outside of grid when the exit is at an edge, and the moved piece is on top of the exit
|
||||
if (_puzzleType == kTileMove && pieceID == 0) {
|
||||
Common::Rect compareRect(newPos.x, newPos.y, newPos.x + _pieces[pieceID]._w, newPos.y + _pieces[pieceID]._h);
|
||||
if (compareRect.contains(_tileMoveExitPos)) {
|
||||
if (horizontal && (_tileMoveExitPos.x == 0 || _tileMoveExitPos.x == (int)_grid[0].size() - 1)) {
|
||||
newPos.x += inc * _tileMoveExitSize;
|
||||
} else if (!horizontal && (_tileMoveExitPos.y == 0 || _tileMoveExitPos.y == (int)_grid.size() - 1)) {
|
||||
newPos.y += inc * _tileMoveExitSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newPos;
|
||||
}
|
||||
|
||||
Common::Rect CollisionPuzzle::getScreenPosition(Common::Point gridPos) {
|
||||
Common::Rect dest = _pieceSrcs[0];
|
||||
|
||||
dest.moveTo(0, 0);
|
||||
|
||||
dest.right -= 1;
|
||||
dest.bottom -= 1;
|
||||
|
||||
if (_puzzleType == kTileMove) {
|
||||
dest.setWidth(dest.width() / 2);
|
||||
}
|
||||
|
||||
dest.moveTo(_gridPos);
|
||||
dest.translate(gridPos.x * dest.width(), gridPos.y *dest.height());
|
||||
dest.translate(gridPos.x * _lineWidth, gridPos.y * _lineWidth);
|
||||
|
||||
dest.right += 1;
|
||||
dest.bottom += 1;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
void CollisionPuzzle::drawGrid() {
|
||||
// Improvement: original rendering does not line up with the grid on either difficulty, but ours does
|
||||
// The differences are marked below
|
||||
for (uint y = 0; y < _grid.size(); ++y) {
|
||||
for (uint x = 0; x < _grid[y].size(); ++x) {
|
||||
uint16 cell = _grid[y][x];
|
||||
Common::Rect cellRect = getScreenPosition(Common::Point(x, y));
|
||||
Common::Point dest(cellRect.left, cellRect.top);
|
||||
|
||||
switch (cell) {
|
||||
case kBlock :
|
||||
|
||||
if (_lineWidth != 6) { // Improvement
|
||||
dest.x += 1;
|
||||
dest.y += 1;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _blockSrc, dest);
|
||||
break;
|
||||
case kWallLeft :
|
||||
dest.x -= _lineWidth - _lineWidth / 6;
|
||||
dest.y = cellRect.top + (cellRect.height() - _verticalWallSrc.height()) / 2;
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest);
|
||||
|
||||
break;
|
||||
case kWallRight :
|
||||
dest.x = cellRect.right - 1 + _lineWidth / 6;
|
||||
dest.y = cellRect.top + (cellRect.height() - _verticalWallSrc.height()) / 2;
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest);
|
||||
|
||||
break;
|
||||
case kWallUp :
|
||||
dest.x += (cellRect.width() - _horizontalWallSrc.width()) / 2;
|
||||
dest.y -= _lineWidth - _lineWidth / 6;
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest);
|
||||
|
||||
break;
|
||||
case kWallDown :
|
||||
dest.x += (cellRect.width() - _horizontalWallSrc.width()) / 2;
|
||||
dest.y = cellRect.bottom - 1 + _lineWidth / 6;
|
||||
|
||||
if (_lineWidth != 6) { // Improvement
|
||||
++dest.y;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest);
|
||||
|
||||
break;
|
||||
default :
|
||||
if (cell == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_lineWidth == 6) { // Improvement
|
||||
dest.x -= 1;
|
||||
} else {
|
||||
dest.x += 1;
|
||||
dest.y += 1;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _homeSrcs[cell - 1], dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void CollisionPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_usesExitButton) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image, _exitButtonSrc, _exitButtonDest);
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->loadSound(_exitButtonSound);
|
||||
g_nancy->_sound->playSound(_exitButtonSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentlyAnimating != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Common::Point checkPos;
|
||||
Common::Rect left, right, up, down;
|
||||
Common::Rect screenPos = _pieces[i].getScreenPosition();
|
||||
|
||||
if (_pieces[i]._w == _pieces[i]._h) {
|
||||
// Width == height, all movement is permitted, hotspots are 10 pixels wide
|
||||
left.setWidth(10);
|
||||
left.setHeight(screenPos.height() - 20);
|
||||
left.moveTo(screenPos.left, screenPos.top + 10);
|
||||
right = left;
|
||||
right.translate(screenPos.width() - 10, 0);
|
||||
|
||||
up.setHeight(10);
|
||||
up.setWidth(screenPos.width() - 20);
|
||||
up.moveTo(screenPos.left + 10, screenPos.top);
|
||||
down = up;
|
||||
down.translate(0, screenPos.height() - 10);
|
||||
} else if (_pieces[i]._w > _pieces[i]._h) {
|
||||
// Width > height, only left/right movement is permitted, hotspots are the size of 1 cell
|
||||
left.setWidth(screenPos.width() / _pieces[i]._w);
|
||||
left.setHeight(screenPos.height() / _pieces[i]._h);
|
||||
left.moveTo(screenPos.left, screenPos.top);
|
||||
right = left;
|
||||
right.translate(right.width() * (_pieces[i]._w - 1), 0);
|
||||
} else {
|
||||
// Width < height, only up/down movement is permitted, hotspots are the size of 1 cell
|
||||
up.setWidth(screenPos.width() / _pieces[i]._w);
|
||||
up.setHeight(screenPos.height() / _pieces[i]._h);
|
||||
up.moveTo(screenPos.left, screenPos.top);
|
||||
down = up;
|
||||
down.translate(0, down.height() * (_pieces[i]._h - 1));
|
||||
}
|
||||
|
||||
if (!left.isEmpty()) {
|
||||
if (left.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallLeft);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveLeft);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!right.isEmpty()) {
|
||||
if (right.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallRight);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveRight);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!up.isEmpty()) {
|
||||
if (up.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallUp);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveUp);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!down.isEmpty()) {
|
||||
if (down.contains(input.mousePos)) {
|
||||
checkPos = movePiece(i, kWallDown);
|
||||
if (checkPos != _pieces[i]._gridPos) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kMoveDown);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_lastPosition = _pieces[i]._gridPos;
|
||||
_pieces[i]._gridPos = checkPos;
|
||||
_currentlyAnimating = i;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
136
engines/nancy/action/puzzle/collisionpuzzle.h
Normal file
136
engines/nancy/action/puzzle/collisionpuzzle.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_COLLISIONPUZZLE_H
|
||||
#define NANCY_ACTION_COLLISIONPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Class responsible for two similar puzzle types, both of which have
|
||||
// rectangular tiles on a grid, which can move up/down/left/right until they
|
||||
// hit a wall or another tile
|
||||
// - CollisionPuzzle: Several 1x1 tiles, each of which has a "home" it needs to reach.
|
||||
// The grid contains walls. Tiles move in all directions.
|
||||
// - TileMovePuzzle: Many differently-sized tiles, one of which must reach the exit.
|
||||
// Rectangular tiles can only move in the directions parallel to their longer sides.
|
||||
// Exit is outside of the tile grid.
|
||||
class CollisionPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum PuzzleType { kCollision, kTileMove };
|
||||
CollisionPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
|
||||
virtual ~CollisionPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
// numbers 1-5 are home IDs, 0 is empty cell
|
||||
enum WallType { kWallLeft = 6, kWallUp = 7, kWallDown = 8, kWallRight = 9, kBlock = 10 };
|
||||
|
||||
class Piece : public RenderObject {
|
||||
public:
|
||||
Piece() : RenderObject(9) {}
|
||||
virtual ~Piece() {}
|
||||
|
||||
Common::Point _gridPos;
|
||||
uint _w = 1;
|
||||
uint _h = 1;
|
||||
|
||||
protected:
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
Common::String getRecordTypeName() const override { return _puzzleType == kCollision ? "CollisionPuzzle" : "TileMovePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Point movePiece(uint pieceID, WallType direction);
|
||||
Common::Rect getScreenPosition(Common::Point gridPos);
|
||||
void drawGrid();
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Array<Common::Array<uint16>> _grid;
|
||||
Common::Array<Common::Point> _startLocations;
|
||||
|
||||
Common::Array<Common::Rect> _pieceSrcs;
|
||||
Common::Array<Common::Rect> _homeSrcs;
|
||||
|
||||
Common::Rect _verticalWallSrc;
|
||||
Common::Rect _horizontalWallSrc;
|
||||
Common::Rect _blockSrc;
|
||||
|
||||
Common::Point _tileMoveExitPos = Common::Point(-1, -1);
|
||||
uint _tileMoveExitSize = 0;
|
||||
|
||||
bool _usesExitButton = false;
|
||||
Common::Rect _exitButtonSrc;
|
||||
Common::Rect _exitButtonDest;
|
||||
|
||||
Common::Point _gridPos;
|
||||
|
||||
uint16 _lineWidth = 0;
|
||||
uint16 _framesPerMove = 0;
|
||||
|
||||
uint32 _timerTime = 0; // in seconds
|
||||
Common::Array<Common::Rect> _timerSrcs;
|
||||
Common::Array<int16> _timerFlagIds;
|
||||
Common::Rect _timerDest;
|
||||
|
||||
SoundDescription _moveSound;
|
||||
SoundDescription _homeSound;
|
||||
SoundDescription _wallHitSound;
|
||||
SoundDescription _exitButtonSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<Piece> _pieces;
|
||||
|
||||
int _currentlyAnimating = -1;
|
||||
int _currentAnimFrame = -1;
|
||||
Common::Point _lastPosition = { -1, -1 };
|
||||
|
||||
uint32 _solveSoundPlayTime = 0;
|
||||
bool _solved = false;
|
||||
|
||||
uint32 _puzzleStartTime = 0;
|
||||
int _currentTimerGraphic = -1;
|
||||
|
||||
PuzzleType _puzzleType;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_COLLISIONPUZZLE_H
|
||||
322
engines/nancy/action/puzzle/cubepuzzle.cpp
Normal file
322
engines/nancy/action/puzzle/cubepuzzle.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/cubepuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void CubePuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
_drawSurface.blitFrom(_image, _pieceSrcs[i], _pieceDests[i]);
|
||||
}
|
||||
|
||||
_placedPieces.resize(5, false);
|
||||
_curRotation = _startRotation;
|
||||
_drawSurface.blitFrom(_image, _placedSrcs[_curRotation][0], _placedDest);
|
||||
}
|
||||
|
||||
void CubePuzzle::registerGraphics() {
|
||||
_curPiece.registerGraphics();
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void CubePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRect(stream, _cwCursorDest);
|
||||
readRect(stream, _ccwCursorDest);
|
||||
|
||||
readRect(stream, _placedDest);
|
||||
|
||||
// four pieces on the side, 1 on top
|
||||
_pieceSrcs.resize(5);
|
||||
_pieceDests.resize(5);
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
readRect(stream, _pieceSrcs[i]);
|
||||
readRect(stream, _pieceDests[i]);
|
||||
}
|
||||
|
||||
_placedSrcs.resize(4);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
readRectArray(stream, _placedSrcs[i], 9);
|
||||
}
|
||||
|
||||
_startRotation = stream.readUint16LE();
|
||||
|
||||
_rotateSound.readNormal(stream);
|
||||
_pickUpSound.readNormal(stream);
|
||||
_placeDownSound.readNormal(stream);
|
||||
|
||||
_solveSceneIDs.resize(4);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_solveSceneIDs[i] = stream.readUint16LE();
|
||||
}
|
||||
_solveScene.readData(stream);
|
||||
_solveSceneIDs[3] = _solveScene._sceneChange.sceneID;
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void CubePuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_rotateSound);
|
||||
g_nancy->_sound->loadSound(_pickUpSound);
|
||||
g_nancy->_sound->loadSound(_placeDownSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
if (!_placedPieces[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_completed = true;
|
||||
|
||||
_state = kActionTrigger;
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_completed) {
|
||||
_solveScene._sceneChange.sceneID = _solveSceneIDs[_curRotation];
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_rotateSound);
|
||||
g_nancy->_sound->stopSound(_pickUpSound);
|
||||
g_nancy->_sound->stopSound(_placeDownSound);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CubePuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
_completed = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_cwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(-1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece == -1 && NancySceneState.getViewport().convertViewportToScreen(_ccwCursorDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp && !g_nancy->_sound->isSoundPlaying(_rotateSound)) {
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotateBase(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 5; ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_pieceDests[i]).contains(input.mousePos)) {
|
||||
if (_pickedUpPiece == -1 && _placedPieces[i]) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (_pickedUpPiece != -1) {
|
||||
_curPiece.putDown();
|
||||
_curPiece.setVisible(false);
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
|
||||
_drawSurface.fillRect(_pieceDests[_pickedUpPiece], _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _pieceSrcs[_pickedUpPiece], _pieceDests[_pickedUpPiece]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != (int)i && !_placedPieces[i]) {
|
||||
// Clicked on another piece while holding, swap them
|
||||
_drawSurface.fillRect(_pieceDests[i], _drawSurface.getTransparentColor());
|
||||
|
||||
if (_pickedUpPiece != -1) {
|
||||
_drawSurface.fillRect(_pieceDests[_pickedUpPiece], _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _pieceSrcs[_pickedUpPiece], _pieceDests[_pickedUpPiece]);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
|
||||
_pickedUpPiece = i;
|
||||
g_nancy->_sound->playSound(_pickUpSound);
|
||||
|
||||
_curPiece.pickUp();
|
||||
_curPiece.setVisible(true);
|
||||
_curPiece._drawSurface.create(_image, _pieceSrcs[i]);
|
||||
_curPiece.setVisible(true);
|
||||
} else {
|
||||
// Clicked the dest of the picked up piece, or an already placed one; simply put it down
|
||||
_pickedUpPiece = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pickedUpPiece != -1) {
|
||||
// Piece picked up
|
||||
_curPiece.handleInput(input);
|
||||
|
||||
bool canPlace = false;
|
||||
|
||||
if (_pickedUpPiece == 0) {
|
||||
// Top piece can ble placed only in the correct rotation (which is 0)
|
||||
canPlace = _curRotation == 0;
|
||||
} else {
|
||||
canPlace = _curRotation + 1 == _pickedUpPiece;
|
||||
}
|
||||
|
||||
if (canPlace) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_placedDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_placedPieces[_pickedUpPiece] = true;
|
||||
_curPiece.putDown();
|
||||
_curPiece.setVisible(false);
|
||||
_pickedUpPiece = -1;
|
||||
g_nancy->_sound->playSound(_placeDownSound);
|
||||
rotateBase(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CubePuzzle::rotateBase(int dir) {
|
||||
_drawSurface.fillRect(_placedDest, _drawSurface.getTransparentColor());
|
||||
|
||||
_curRotation += dir;
|
||||
if (_curRotation < 0) {
|
||||
_curRotation = 3;
|
||||
} else if (_curRotation > 3) {
|
||||
_curRotation = 0;
|
||||
}
|
||||
|
||||
uint srcSelect = 0;
|
||||
|
||||
if (_placedPieces[0]) {
|
||||
if (_placedPieces[_curRotation + 1]) {
|
||||
// Front piece is in, other checks are unnecessary
|
||||
srcSelect = 2;
|
||||
} else {
|
||||
int leftIndex = _curRotation + 1;
|
||||
if (leftIndex > 3) {
|
||||
leftIndex = 0;
|
||||
}
|
||||
|
||||
int rightIndex = _curRotation - 1;
|
||||
if (rightIndex < 0) {
|
||||
rightIndex = 3;
|
||||
}
|
||||
|
||||
int backIndex = (_curRotation + 2) % 4;
|
||||
|
||||
if (_placedPieces[leftIndex + 1]) {
|
||||
// Left piece is in, check for right or back
|
||||
if (_placedPieces[rightIndex + 1]) {
|
||||
// Draw left & right piece
|
||||
srcSelect = 8;
|
||||
} else if (_placedPieces[backIndex]) {
|
||||
// Draw left & back piece
|
||||
srcSelect = 6;
|
||||
} else {
|
||||
// Draw left piece only
|
||||
srcSelect = 3;
|
||||
}
|
||||
} else if (_placedPieces[rightIndex + 1]) {
|
||||
// Right piece is in, check for back
|
||||
if (_placedPieces[backIndex + 1]) {
|
||||
// Draw right & back piece
|
||||
srcSelect = 7;
|
||||
} else {
|
||||
// Draw right piece only
|
||||
srcSelect = 4;
|
||||
}
|
||||
} else if (_placedPieces[backIndex + 1]) {
|
||||
// Draw back piece only
|
||||
srcSelect = 5;
|
||||
} else {
|
||||
// Draw top piece only
|
||||
srcSelect = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _placedSrcs[_curRotation][srcSelect], _placedDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
101
engines/nancy/action/puzzle/cubepuzzle.h
Normal file
101
engines/nancy/action/puzzle/cubepuzzle.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_CUBEPUZZLE_H
|
||||
#define NANCY_ACTION_CUBEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Similar idea to AssemblyPuzzle; the player is provided with broken pieces of
|
||||
// a cube, and has to assemble it back together. However, the data is completely
|
||||
// different to AssemblyPuzzle's, so we need separate implementations.
|
||||
class CubePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
CubePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~CubePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "CubePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void rotateBase(int dir);
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Rect _cwCursorDest;
|
||||
Common::Rect _ccwCursorDest;
|
||||
|
||||
Common::Rect _placedDest;
|
||||
|
||||
Common::Array<Common::Rect> _pieceSrcs; // Used only when not placed
|
||||
Common::Array<Common::Rect> _pieceDests; // Used only when not placed
|
||||
|
||||
// 4 arrays with 9 rects each; every rect corresponds to a specific permutation of placed pieces:
|
||||
// - bottom piece only (start state)
|
||||
// - bottom & top piece
|
||||
// - front piece placed
|
||||
// - left piece placed
|
||||
// - back piece placed
|
||||
// - right piece placed
|
||||
// - left + back piece placed
|
||||
// - right + back piece placed
|
||||
// - left + right piece placed
|
||||
Common::Array<Common::Array<Common::Rect>> _placedSrcs;
|
||||
|
||||
uint16 _startRotation = 0;
|
||||
|
||||
SoundDescription _rotateSound;
|
||||
SoundDescription _pickUpSound;
|
||||
SoundDescription _placeDownSound;
|
||||
|
||||
// Multiple solve scenes, one for each cube orientation
|
||||
Common::Array<uint> _solveSceneIDs;
|
||||
SceneChangeWithFlag _solveScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Misc::MouseFollowObject _curPiece;
|
||||
|
||||
Common::Array<bool> _placedPieces;
|
||||
int _pickedUpPiece = -1;
|
||||
|
||||
int _curRotation = 0;
|
||||
bool _completed = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_CUBEPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/cuttingpuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/cuttingpuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/cuttingpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void CuttingPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void CuttingPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 8 Art Studio Lathe Puzzle");
|
||||
NancySceneState.setEventFlag(341, g_nancy->_true); // Set dowel puzzle flag to solved
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 3854;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void CuttingPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void CuttingPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/cuttingpuzzle.h
Normal file
51
engines/nancy/action/puzzle/cuttingpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_CUTTINGPUZZLE_H
|
||||
#define NANCY_ACTION_CUTTINGPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Art Studio Lathe Puzzle in Nancy 8
|
||||
|
||||
class CuttingPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
CuttingPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~CuttingPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "CuttingPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_CUTTINGPUZZLE_H
|
||||
473
engines/nancy/action/puzzle/hamradiopuzzle.cpp
Normal file
473
engines/nancy/action/puzzle/hamradiopuzzle.cpp
Normal file
@@ -0,0 +1,473 @@
|
||||
/* 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 "common/random.h"
|
||||
#include "common/config-manager.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/hamradiopuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
static const char *morseCodeTable[] = {
|
||||
".-", "-...", "-.-.", "-..", // a, b, c, d
|
||||
".", "..-.", "--.", "....", // e, f, g, h
|
||||
"..", ".---", "-.-", ".-..", // i, j, k, l
|
||||
"--", "-.", "---", ".--.", // m, n, o, p
|
||||
"--.-", ".-.", "...", "-", // q, r, s, t
|
||||
"..-", "...-", ".--", "-..-", // u, v, w, x
|
||||
"-.--", "--.." // y, z
|
||||
};
|
||||
|
||||
void HamRadioPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::updateGraphics() {
|
||||
if (_digitsRolling) {
|
||||
uint32 curTime = g_nancy->getTotalPlayTime();
|
||||
bool allDigitsCorrect = true;
|
||||
|
||||
for (uint i = 0; i < _numDigits; ++i) {
|
||||
if (curTime > _nextDigitFrameTimes[i]) {
|
||||
uint targetFrame = (_curDigits[i] == 0 ? (10 - 1) * 3 : (_curDigits[i] - 1) * 3);
|
||||
|
||||
if (_displayedDigitFrames[i] == targetFrame) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++_displayedDigitFrames[i] >= 10 * 3) {
|
||||
_displayedDigitFrames[i] = 0;
|
||||
}
|
||||
|
||||
// Have we arrived at the correct digit?
|
||||
if (_displayedDigitFrames[i] != targetFrame) {
|
||||
// If not, set the next frame time depending on how far away the next digit is
|
||||
// This way, the animation slows down as we approach the correct digit
|
||||
int frameDifference = targetFrame - _displayedDigitFrames[i];
|
||||
if (frameDifference < 0) {
|
||||
frameDifference += 10 * 3;
|
||||
}
|
||||
|
||||
if (_nextDigitFrameTimes[i] == 0) {
|
||||
_nextDigitFrameTimes[i] = curTime;
|
||||
}
|
||||
|
||||
switch (frameDifference) {
|
||||
case 1:
|
||||
_nextDigitFrameTimes[i] += 300; break;
|
||||
case 2:
|
||||
// fall through
|
||||
case 3:
|
||||
_nextDigitFrameTimes[i] += 200; break;
|
||||
case 4:
|
||||
// fall through
|
||||
case 5:
|
||||
_nextDigitFrameTimes[i] += 100; break;
|
||||
default:
|
||||
_nextDigitFrameTimes[i] += 50; break;
|
||||
}
|
||||
|
||||
// Mark digits as incorrect
|
||||
allDigitsCorrect = false;
|
||||
}
|
||||
|
||||
// Play the rolling sound
|
||||
g_nancy->_sound->loadSound(_digitRollSound, nullptr, true);
|
||||
g_nancy->_sound->playSound(_digitRollSound);
|
||||
|
||||
// Finally, change the digit graphic
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[_displayedDigitFrames[i]], _digitDests[i]);
|
||||
_needsRedraw = true;
|
||||
} else {
|
||||
// Still animating a digit, so we can't be at the correct one yet
|
||||
allDigitsCorrect = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allDigitsCorrect) {
|
||||
// We've arrived at the correct digits, end the animation state
|
||||
_digitsRolling = false;
|
||||
Common::fill(_nextDigitFrameTimes.begin(), _nextDigitFrameTimes.end(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::setFrequency(const Common::Array<uint16> &freq) {
|
||||
_isOnCorrectFrequency = false;
|
||||
_curMorseString.clear();
|
||||
_curCharString.clear();
|
||||
|
||||
if (freq == _startFreq.frequency) {
|
||||
// Check start frequency
|
||||
_startFreq.sound.loadAndPlay();
|
||||
NancySceneState.setEventFlag(_startFreq.flag);
|
||||
} else if (freq == _correctFreq.frequency) {
|
||||
// Check correct transmission frequency
|
||||
_correctFreq.sound.loadAndPlay();
|
||||
NancySceneState.setEventFlag(_correctFreq.flag);
|
||||
_isOnCorrectFrequency = true;
|
||||
} else {
|
||||
// Check other frequencies
|
||||
for (auto &otherFreq : _otherFrequencies) {
|
||||
if (freq == otherFreq.frequency) {
|
||||
otherFreq.sound.loadAndPlay();
|
||||
NancySceneState.setEventFlag(otherFreq.flag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No frequency found, pick random "bad" sound
|
||||
// This is re-rolled every time a bad frequency is connected to, even if the player
|
||||
// hasn't inputted any new digits
|
||||
_badFrequencySounds[g_nancy->_randomSource->getRandomNumber(_badFrequencySounds.size() - 1)].loadAndPlay();
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::CCSound::readData(Common::SeekableReadStream &stream) {
|
||||
char buf[100];
|
||||
stream.read(buf, 100);
|
||||
assembleTextLine(buf, text, 100);
|
||||
sound.readNormal(stream);
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::CCSound::loadAndPlay() {
|
||||
g_nancy->_sound->loadSound(sound);
|
||||
g_nancy->_sound->playSound(sound);
|
||||
|
||||
if (text.size() && ConfMan.getBool("subtitles")) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(text);
|
||||
NancySceneState.getTextbox().drawTextbox();
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::Frequency::readData(Common::SeekableReadStream &stream, uint16 numDigits) {
|
||||
frequency.resize(numDigits);
|
||||
for (uint i = 0; i < numDigits; ++i) {
|
||||
frequency[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((8 - numDigits) * 2);
|
||||
|
||||
sound.readData(stream);
|
||||
flag.label = stream.readUint16LE();
|
||||
flag.flag = stream.readByte();
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numDigits = stream.readUint16LE();
|
||||
|
||||
_startFreq.readData(stream, _numDigits);
|
||||
_correctFreq.readData(stream, _numDigits);
|
||||
|
||||
_passwordMaxSize = stream.readUint16LE();
|
||||
readFilename(stream, _password); // not a filename
|
||||
_passwordFlag.label = stream.readUint16LE();
|
||||
_passwordFlag.flag = stream.readByte();
|
||||
readFilename(stream, _codeWord); // not a filename
|
||||
|
||||
stream.skip(2);
|
||||
|
||||
readRectArray(stream, _digitDests, _numDigits, 8);
|
||||
readRectArray(stream, _buttonDests, 10 + 6);
|
||||
|
||||
readRectArray(stream, _digitSrcs, 10 * 3); // digits 0-9, plus 2 inbetweens per digit
|
||||
readRectArray(stream, _buttonSrcs, 10 + 6);
|
||||
|
||||
_digitRollSound.readNormal(stream);
|
||||
_frequencyButtonSound.readData(stream);
|
||||
_connectButtonSound.readData(stream);
|
||||
_dotButtonSound.readData(stream);
|
||||
_dashButtonSound.readData(stream);
|
||||
_sendButtonSound.readData(stream);
|
||||
_deleteButtonSound.readData(stream);
|
||||
_resetButtonSound.readData(stream);
|
||||
_badLetterSound.readData(stream);
|
||||
_longMorseOtherSound.readData(stream);
|
||||
_goodPasswordSound.readData(stream);
|
||||
_longMorseSound.readData(stream);
|
||||
|
||||
_badFrequencySounds.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_badFrequencySounds[i].readData(stream);
|
||||
}
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readData(stream);
|
||||
|
||||
readRect(stream, _exitButtonDest);
|
||||
readRect(stream, _exitButtonSrc);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
_exitSoundDelay = stream.readUint16LE();
|
||||
_exitSound.readNormal(stream);
|
||||
|
||||
uint16 numOtherFreqs = stream.readUint16LE();
|
||||
_otherFrequencies.resize(numOtherFreqs);
|
||||
for (uint i = 0; i < numOtherFreqs; ++i) {
|
||||
_otherFrequencies[i].readData(stream, _numDigits);
|
||||
}
|
||||
|
||||
_curDigits.resize(_numDigits, 0);
|
||||
_displayedDigitFrames.resize(_numDigits, 0);
|
||||
_nextDigitFrameTimes.resize(_numDigits, 0);
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_digitRollSound);
|
||||
setFrequency(_startFreq.frequency);
|
||||
_curDigits = _startFreq.frequency;
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_pressedButton != kNone && g_nancy->getTotalPlayTime() > _buttonEndTime) {
|
||||
bool isDot = false;
|
||||
|
||||
// Check for button presses
|
||||
switch (_pressedButton) {
|
||||
case kConnect:
|
||||
setFrequency(_curDigits);
|
||||
break;
|
||||
case kDot:
|
||||
isDot = true;
|
||||
// fall through
|
||||
case kDash:
|
||||
_curMorseString += isDot ? '.' : '-'; // Original engine uses the captions inside the dot and dash sounds
|
||||
|
||||
if (_curMorseString.size() > 4) {
|
||||
_curMorseString.clear();
|
||||
_badLetterSound.loadAndPlay();
|
||||
} else {
|
||||
if (ConfMan.getBool("subtitles")) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_curMorseString);
|
||||
NancySceneState.getTextbox().setOverrideFont(3); // Original engine pushes <f3> tag instead
|
||||
NancySceneState.getTextbox().drawTextbox();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kSend: {
|
||||
bool foundCorrect = false;
|
||||
|
||||
if (_curMorseString.size()) {
|
||||
for (uint i = 0; i < ARRAYSIZE(morseCodeTable); ++i) {
|
||||
if (_curMorseString == morseCodeTable[i]) {
|
||||
foundCorrect = true;
|
||||
_curCharString += ('a' + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if above maximum length string
|
||||
if (_curMorseString.size() > 10) {
|
||||
_longMorseSound.loadAndPlay();
|
||||
_curCharString.clear();
|
||||
_curMorseString.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Morse code is incorrect
|
||||
if (!foundCorrect) {
|
||||
_badLetterSound.loadAndPlay();
|
||||
}
|
||||
}
|
||||
|
||||
// fall through
|
||||
case kDelete:
|
||||
_curMorseString.clear();
|
||||
|
||||
if (_curCharString.size() > 10) {
|
||||
// Password is above max size, clear
|
||||
_curCharString.clear();
|
||||
NancySceneState.getTextbox().clear();
|
||||
_longMorseSound.loadAndPlay();
|
||||
} else if (_solvedPassword && _curCharString.size() > _passwordMaxSize) {
|
||||
// Password is above max size, clear
|
||||
_curCharString.clear();
|
||||
NancySceneState.getTextbox().clear();
|
||||
_longMorseOtherSound.loadAndPlay();
|
||||
}
|
||||
|
||||
if (ConfMan.getBool("subtitles")) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_curCharString);
|
||||
NancySceneState.getTextbox().drawTextbox();
|
||||
}
|
||||
|
||||
if (_isOnCorrectFrequency) {
|
||||
// When transmitting on right frequency, check password/code word
|
||||
if (!_solvedPassword) {
|
||||
// Password not solved, check against it
|
||||
if (_curCharString == _password) {
|
||||
_solvedPassword = true;
|
||||
NancySceneState.setEventFlag(_passwordFlag);
|
||||
_curCharString.clear();
|
||||
_goodPasswordSound.loadAndPlay();
|
||||
}
|
||||
} else {
|
||||
// Password solved, check against codeword
|
||||
if (_curCharString == _codeWord) {
|
||||
_solvedCodeword = true;
|
||||
_curCharString.clear();
|
||||
|
||||
_solveSound.loadAndPlay(); // Sound delay is ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kReset:
|
||||
_curCharString.clear();
|
||||
_curMorseString.clear();
|
||||
NancySceneState.getTextbox().clear();
|
||||
|
||||
break;
|
||||
default:
|
||||
// Number digit
|
||||
for (int i = 0; i < _numDigits - 1; ++i) {
|
||||
_curDigits[i] = _curDigits[i + 1];
|
||||
}
|
||||
|
||||
_curDigits.back() = (_pressedButton == 9 ? 0 : _pressedButton + 1);
|
||||
_digitsRolling = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_drawSurface.fillRect(_buttonDests[_pressedButton], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
|
||||
_pressedButton = kNone;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_digitsRolling) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_solvedCodeword) {
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
// Fail sound is ignored
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HamRadioPuzzle::handleInput(NancyInput &input) {
|
||||
if (_digitsRolling || _state != kRun || _pressedButton != kNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle exit button
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
for (uint i = 0; i < _curDigits.size(); ++i) {
|
||||
_curDigits[i] = 0;
|
||||
}
|
||||
_digitsRolling = true;
|
||||
_drawSurface.blitFrom(_image, _exitButtonSrc, _exitButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle other buttons
|
||||
for (uint i = 0; i < _buttonDests.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[i]).contains(input.mousePos)) {
|
||||
if (i >= 10 || _pressedButton == kNone) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_pressedButton = i;
|
||||
_drawSurface.blitFrom(_image, _buttonSrcs[i], _buttonDests[i]);
|
||||
_needsRedraw = true;
|
||||
|
||||
CCSound *soundToPlay = nullptr;
|
||||
|
||||
switch (i) {
|
||||
case kConnect:
|
||||
soundToPlay = &_connectButtonSound; break;
|
||||
case kDot:
|
||||
soundToPlay = &_dotButtonSound; break;
|
||||
case kDash:
|
||||
soundToPlay = &_dashButtonSound; break;
|
||||
case kSend:
|
||||
soundToPlay = &_sendButtonSound; break;
|
||||
case kDelete:
|
||||
soundToPlay = &_deleteButtonSound; break;
|
||||
case kReset:
|
||||
soundToPlay = &_resetButtonSound; break;
|
||||
default:
|
||||
soundToPlay = &_frequencyButtonSound; break;
|
||||
}
|
||||
|
||||
// Do NOT use the loadAndPlaySound() function, since the dot/dash sounds have ./- captions
|
||||
g_nancy->_sound->loadSound(soundToPlay->sound, nullptr, true);
|
||||
g_nancy->_sound->playSound(soundToPlay->sound);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pressedButton != kNone) {
|
||||
_buttonEndTime = g_nancy->getTotalPlayTime() + 250;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
137
engines/nancy/action/puzzle/hamradiopuzzle.h
Normal file
137
engines/nancy/action/puzzle/hamradiopuzzle.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_HAMRADIOPUZZLE_H
|
||||
#define NANCY_ACTION_HAMRADIOPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// A puzzle that has the player input radio frequencies, and
|
||||
// send morse code data via ham radio. Used in nancy6.
|
||||
class HamRadioPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
HamRadioPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~HamRadioPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "HamRadioPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void setFrequency(const Common::Array<uint16> &freq);
|
||||
|
||||
// 0-10 are the digit buttons
|
||||
enum ButtonPress { kNone = -1, kConnect = 10, kDot = 11, kDash = 12, kSend = 13, kDelete = 14, kReset = 15 };
|
||||
|
||||
struct CCSound {
|
||||
Common::String text;
|
||||
SoundDescription sound;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream);
|
||||
void loadAndPlay();
|
||||
};
|
||||
|
||||
struct Frequency {
|
||||
Common::Array<uint16> frequency;
|
||||
CCSound sound;
|
||||
FlagDescription flag;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream, uint16 numDigits);
|
||||
};
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _numDigits = 0;
|
||||
|
||||
Frequency _startFreq;
|
||||
Frequency _correctFreq;
|
||||
|
||||
uint16 _passwordMaxSize = 0;
|
||||
Common::String _password;
|
||||
FlagDescription _passwordFlag;
|
||||
Common::String _codeWord;
|
||||
|
||||
Common::Array<Common::Rect> _digitDests;
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
|
||||
Common::Array<Common::Rect> _digitSrcs;
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
|
||||
SoundDescription _digitRollSound;
|
||||
CCSound _frequencyButtonSound;
|
||||
CCSound _connectButtonSound;
|
||||
CCSound _dotButtonSound;
|
||||
CCSound _dashButtonSound;
|
||||
CCSound _sendButtonSound;
|
||||
CCSound _deleteButtonSound;
|
||||
CCSound _resetButtonSound;
|
||||
CCSound _badLetterSound;
|
||||
CCSound _longMorseOtherSound;
|
||||
CCSound _goodPasswordSound;
|
||||
CCSound _longMorseSound;
|
||||
Common::Array<CCSound> _badFrequencySounds;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0; // not used
|
||||
CCSound _solveSound;
|
||||
|
||||
Common::Rect _exitButtonDest;
|
||||
Common::Rect _exitButtonSrc;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
uint16 _exitSoundDelay = 0; // not used
|
||||
SoundDescription _exitSound; // not used
|
||||
|
||||
Common::Array<Frequency> _otherFrequencies;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
// Frequency display data
|
||||
bool _digitsRolling = true;
|
||||
Common::Array<uint16> _curDigits;
|
||||
Common::Array<uint16> _displayedDigitFrames;
|
||||
Common::Array<uint32> _nextDigitFrameTimes;
|
||||
|
||||
// Sent morse code
|
||||
Common::String _curMorseString;
|
||||
Common::String _curCharString;
|
||||
|
||||
int _pressedButton = kNone;
|
||||
uint32 _buttonEndTime = 0;
|
||||
|
||||
bool _isOnCorrectFrequency = false;
|
||||
bool _solvedPassword = false;
|
||||
bool _solvedCodeword = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_HAMRADIOPUZZLE_H
|
||||
231
engines/nancy/action/puzzle/leverpuzzle.cpp
Normal file
231
engines/nancy/action/puzzle/leverpuzzle.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/leverpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void LeverPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
}
|
||||
|
||||
void LeverPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_srcRects.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_srcRects.push_back(Common::Array<Common::Rect>());
|
||||
_srcRects.back().reserve(3);
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
_srcRects.back().push_back(Common::Rect());
|
||||
readRect(stream, _srcRects.back().back());
|
||||
}
|
||||
}
|
||||
|
||||
_destRects.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_destRects.push_back(Common::Rect());
|
||||
readRect(stream, _destRects.back());
|
||||
|
||||
if (leverID == 0) {
|
||||
_screenPosition = _destRects.back();
|
||||
} else {
|
||||
_screenPosition.extend(_destRects.back());
|
||||
}
|
||||
}
|
||||
|
||||
_playerSequence.reserve(3);
|
||||
_leverDirection.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_playerSequence.push_back(stream.readByte());
|
||||
_leverDirection.push_back(true);
|
||||
}
|
||||
|
||||
_correctSequence.reserve(3);
|
||||
for (uint leverID = 0; leverID < 3; ++leverID) {
|
||||
_correctSequence.push_back(stream.readByte());
|
||||
}
|
||||
|
||||
_moveSound.readNormal(stream);
|
||||
_noMoveSound.readNormal(stream);
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void LeverPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
g_nancy->_sound->loadSound(_noMoveSound);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
drawLever(i);
|
||||
}
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
if (_playerSequence[i] != _correctSequence[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_solveState = kPlaySound;
|
||||
break;
|
||||
case kPlaySound:
|
||||
if (g_nancy->getTotalPlayTime() <= _solveSoundPlayTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
g_nancy->_sound->stopSound(_moveSound);
|
||||
g_nancy->_sound->stopSound(_noMoveSound);
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
_exitScene.execute();
|
||||
} else {
|
||||
NancySceneState.changeScene(_solveExitScene._sceneChange);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void LeverPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
bool isMoving = false;
|
||||
// Hardcoded by the original engine
|
||||
switch (i) {
|
||||
case 0:
|
||||
isMoving = true;
|
||||
break;
|
||||
case 1:
|
||||
if (_playerSequence[0] == 1) {
|
||||
isMoving = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case 2:
|
||||
if (_playerSequence[0] == 2) {
|
||||
isMoving = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (isMoving) {
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
|
||||
if (_leverDirection[i]) {
|
||||
// Moving down
|
||||
if (_playerSequence[i] == 3) {
|
||||
--_playerSequence[i];
|
||||
_leverDirection[i] = false;
|
||||
} else {
|
||||
++_playerSequence[i];
|
||||
}
|
||||
} else {
|
||||
// Moving up
|
||||
if (_playerSequence[i] == 0) {
|
||||
++_playerSequence[i];
|
||||
_leverDirection[i] = true;
|
||||
} else {
|
||||
--_playerSequence[i];
|
||||
}
|
||||
}
|
||||
|
||||
drawLever(i);
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_noMoveSound);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LeverPuzzle::drawLever(uint id) {
|
||||
Common::Point destPoint(_destRects[id].left - _screenPosition.left, _destRects[id].top - _screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _srcRects[id][_playerSequence[id]], destPoint);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
70
engines/nancy/action/puzzle/leverpuzzle.h
Normal file
70
engines/nancy/action/puzzle/leverpuzzle.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_LEVERPUZZLE_H
|
||||
#define NANCY_ACTION_LEVERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class LeverPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
|
||||
LeverPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~LeverPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Array<Common::Rect>> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<byte> _correctSequence;
|
||||
SoundDescription _moveSound;
|
||||
SoundDescription _noMoveSound;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Common::Array<byte> _playerSequence;
|
||||
Common::Array<bool> _leverDirection;
|
||||
Graphics::ManagedSurface _image;
|
||||
Time _solveSoundPlayTime;
|
||||
SolveState _solveState = kNotSolved;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "LeverPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawLever(uint id);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_LEVERPUZZLE_H
|
||||
96
engines/nancy/action/puzzle/matchpuzzle.cpp
Normal file
96
engines/nancy/action/puzzle/matchpuzzle.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/matchpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MatchPuzzle::init() {
|
||||
// TODO
|
||||
//_screenPosition = _displayBounds;
|
||||
|
||||
//_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
//_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_overlayName, _image);
|
||||
RenderActionRecord::init();
|
||||
}
|
||||
|
||||
void MatchPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - return to the main menu
|
||||
warning("STUB - Nancy 8 flag game");
|
||||
_exitSceneChange.execute();
|
||||
}
|
||||
|
||||
void MatchPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _overlayName);
|
||||
readFilename(stream, _flagPointBackgroundName);
|
||||
|
||||
stream.skip(2); // TODO (value: 5)
|
||||
stream.skip(2); // TODO (value: 7)
|
||||
stream.skip(2); // 26 flags
|
||||
|
||||
readRect(stream,_shuffleButtonRect);
|
||||
readRectArray(stream, _flagRects, 26);
|
||||
|
||||
stream.skip(103); // TODO (mostly zeroes)
|
||||
|
||||
readFilenameArray(stream, _flagNames, 26);
|
||||
|
||||
stream.skip(132); // TODO (zeroes)
|
||||
stream.skip(173); // TODO
|
||||
|
||||
_slotWinSound.readNormal(stream);
|
||||
_shuffleSound.readNormal(stream);
|
||||
_cardPlaceSound.readNormal(stream);
|
||||
|
||||
_solveSceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_matchSuccessSound.readNormal(stream);
|
||||
_exitSceneChange.readData(stream);
|
||||
|
||||
stream.skip(16); // TODO
|
||||
}
|
||||
|
||||
void MatchPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
70
engines/nancy/action/puzzle/matchpuzzle.h
Normal file
70
engines/nancy/action/puzzle/matchpuzzle.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MATCHPUZZLE_H
|
||||
#define NANCY_ACTION_MATCHPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Flag puzzle in Nancy 8
|
||||
|
||||
class MatchPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MatchPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MatchPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MatchPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
//Common::Rect _displayBounds;
|
||||
|
||||
Common::Rect _shuffleButtonRect;
|
||||
Common::Array<Common::Rect> _flagRects;
|
||||
|
||||
Common::Path _overlayName;
|
||||
Common::Path _flagPointBackgroundName;
|
||||
|
||||
Common::StringArray _flagNames;
|
||||
|
||||
SoundDescription _slotWinSound;
|
||||
SoundDescription _shuffleSound;
|
||||
SoundDescription _cardPlaceSound;
|
||||
SoundDescription _matchSuccessSound;
|
||||
|
||||
SceneChangeWithFlag _solveSceneChange;
|
||||
SceneChangeWithFlag _exitSceneChange;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MATCHPUZZLE_H
|
||||
572
engines/nancy/action/puzzle/mazechasepuzzle.cpp
Normal file
572
engines/nancy/action/puzzle/mazechasepuzzle.cpp
Normal file
@@ -0,0 +1,572 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/mazechasepuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MazeChasePuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
for (uint i = 0; i < _startLocations.size(); ++i) {
|
||||
_pieces.push_back(Piece(_z + i + 1));
|
||||
_pieces[i]._drawSurface.create(_image, i == 0 ? _playerSrc : _enemySrc);
|
||||
Common::Rect pos = getScreenPosition(_startLocations[i]);
|
||||
_pieces[i].moveTo(pos);
|
||||
_pieces[i]._gridPos = _startLocations[i];
|
||||
_pieces[i]._lastPos = _pieces[i]._gridPos;
|
||||
_pieces[i].setVisible(true);
|
||||
_pieces[i].setTransparent(true);
|
||||
}
|
||||
|
||||
if (NancySceneState.getEventFlag(_solveScene._flag)) {
|
||||
_drawSurface.blitFrom(_image, _lightSrc, _lightDest);
|
||||
}
|
||||
|
||||
drawGrid();
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::registerGraphics() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i].registerGraphics();
|
||||
}
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::updateGraphics() {
|
||||
if (_currentAnimFrame != -1) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_moveSound) || g_nancy->_sound->isSoundPlaying(_failSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Framerate-dependent animation. Should be fine since we limit the engine to ~60fps
|
||||
++_currentAnimFrame;
|
||||
|
||||
if (_reset) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
Piece &cur = _pieces[i];
|
||||
if (cur._gridPos != cur._lastPos) {
|
||||
bool horizontal = cur._gridPos.x != cur._lastPos.x;
|
||||
|
||||
Common::Rect destRect = getScreenPosition(cur._lastPos);
|
||||
Common::Rect endPos = getScreenPosition(cur._gridPos);
|
||||
|
||||
// Make sure to adjust the frame id for enemies
|
||||
int frame = (i == 0) ? _currentAnimFrame : _currentAnimFrame - 1;
|
||||
|
||||
Common::Point dest(destRect.left, destRect.top);
|
||||
if (horizontal) {
|
||||
dest.x = destRect.left + (endPos.left - dest.x) * frame / _framesPerMove;
|
||||
} else {
|
||||
dest.y = destRect.top + (endPos.top - dest.y) * frame / _framesPerMove;
|
||||
}
|
||||
|
||||
cur.moveTo(dest);
|
||||
|
||||
if (frame == _framesPerMove) {
|
||||
cur._lastPos = cur._gridPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentAnimFrame > 0) {
|
||||
if (!_solved) {
|
||||
// Make sure not to move pieces when the player is about to lose
|
||||
bool playerRanIntoEnemy = false;
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (_pieces[0]._gridPos == _pieces[i]._gridPos) {
|
||||
playerRanIntoEnemy = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!playerRanIntoEnemy) {
|
||||
// Each enemy moves one frame after the last one
|
||||
enemyMovement(_currentAnimFrame);
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentAnimFrame == 1) {
|
||||
// Clear the buttons
|
||||
Common::Rect fill = _upButtonDest;
|
||||
fill.extend(_downButtonDest);
|
||||
fill.extend(_leftButtonDest);
|
||||
fill.extend(_rightButtonDest);
|
||||
fill.extend(_resetButtonDest);
|
||||
_drawSurface.fillRect(fill, _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
} else if (_currentAnimFrame >= _framesPerMove + 1) {
|
||||
_currentAnimFrame = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint width = stream.readUint16LE();
|
||||
uint height = stream.readUint16LE();
|
||||
uint numEnemies = stream.readUint16LE();
|
||||
|
||||
_exitPos.x = stream.readUint16LE();
|
||||
_exitPos.y = stream.readUint16LE();
|
||||
|
||||
_grid.resize(height, Common::Array<uint16>(width));
|
||||
for (uint y = 0; y < height; ++y) {
|
||||
for (uint x = 0; x < width; ++x) {
|
||||
_grid[y][x] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((8 - width) * 2);
|
||||
}
|
||||
stream.skip((8 - height) * 8 * 2);
|
||||
|
||||
_startLocations.resize(numEnemies + 1);
|
||||
for (uint i = 0; i < _startLocations.size(); ++i) {
|
||||
_startLocations[i].x = stream.readUint16LE();
|
||||
_startLocations[i].y = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRect(stream, _playerSrc);
|
||||
readRect(stream, _enemySrc);
|
||||
readRect(stream, _verticalWallSrc);
|
||||
readRect(stream, _horizontalWallSrc);
|
||||
readRect(stream, _lightSrc);
|
||||
|
||||
readRect(stream, _upButtonSrc);
|
||||
readRect(stream, _rightButtonSrc);
|
||||
readRect(stream, _downButtonSrc);
|
||||
readRect(stream, _leftButtonSrc);
|
||||
readRect(stream, _resetButtonSrc);
|
||||
|
||||
_gridPos.x = stream.readUint32LE();
|
||||
_gridPos.y = stream.readUint32LE();
|
||||
|
||||
readRect(stream, _lightDest);
|
||||
|
||||
readRect(stream, _upButtonDest);
|
||||
readRect(stream, _rightButtonDest);
|
||||
readRect(stream, _downButtonDest);
|
||||
readRect(stream, _leftButtonDest);
|
||||
readRect(stream, _resetButtonDest);
|
||||
|
||||
_lineWidth = stream.readUint16LE();
|
||||
_framesPerMove = stream.readUint16LE();
|
||||
|
||||
_failSound.readNormal(stream);
|
||||
_moveSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_moveSound);
|
||||
g_nancy->_sound->loadSound(_failSound);
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_currentAnimFrame != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pieces[0]._gridPos == _exitPos) {
|
||||
_pieces[0]._gridPos = _exitPos + Common::Point(_exitPos.x == 0 ? -1 : 1, 0);
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
} else {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (_pieces[i]._gridPos == _pieces[0]._gridPos) {
|
||||
g_nancy->_sound->playSound(_failSound);
|
||||
++_currentAnimFrame;
|
||||
_reset = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_solveSoundPlayTime == 0) {
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
return;
|
||||
} else if (_solveSoundPlayTime < g_nancy->getTotalPlayTime()) {
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_moveSound);
|
||||
g_nancy->_sound->stopSound(_failSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _solved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentAnimFrame != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect buttonHotspot = _upButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallUp)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
--_pieces[0]._gridPos.y;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _upButtonSrc, _upButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _rightButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallRight)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_pieces[0]._gridPos.x;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _rightButtonSrc, _rightButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _downButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallDown)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_pieces[0]._gridPos.y;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _downButtonSrc, _downButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _leftButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
if (canMove(0, kWallLeft)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
--_pieces[0]._gridPos.x;
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _leftButtonSrc, _leftButtonDest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buttonHotspot = _resetButtonDest;
|
||||
buttonHotspot.grow(-10);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(buttonHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
++_currentAnimFrame;
|
||||
g_nancy->_sound->playSound(_moveSound);
|
||||
_drawSurface.blitFrom(_image, _resetButtonSrc, _resetButtonDest);
|
||||
_needsRedraw = true;
|
||||
_reset = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect MazeChasePuzzle::getScreenPosition(Common::Point gridPos) {
|
||||
Common::Rect dest = _playerSrc;
|
||||
|
||||
dest.moveTo(0, 0);
|
||||
|
||||
dest.right -= 1;
|
||||
dest.bottom -= 1;
|
||||
|
||||
dest.moveTo(_gridPos);
|
||||
dest.translate(gridPos.x * _lineWidth, gridPos.y * _lineWidth);
|
||||
dest.translate(gridPos.x * dest.width(), gridPos.y *dest.height());
|
||||
|
||||
if (gridPos.x < 0 || gridPos.x >= (int)_grid[0].size()) {
|
||||
// Make sure the end position is in the middle of the dancers
|
||||
dest.translate(12, 0);
|
||||
}
|
||||
|
||||
dest.right += 1;
|
||||
dest.bottom += 1;
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::drawGrid() {
|
||||
for (uint y = 0; y < _grid.size(); ++y) {
|
||||
for (uint x = 0; x < _grid[y].size(); ++x) {
|
||||
uint16 cell = _grid[y][x];
|
||||
Common::Rect cellRect = getScreenPosition(Common::Point(x, y));
|
||||
Common::Point dest(cellRect.left, cellRect.top);
|
||||
|
||||
if (cell == kWallUp || cell == kWallUpDown) {
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest - Common::Point(0, _lineWidth));
|
||||
}
|
||||
|
||||
if (cell == kWallDown || cell == kWallUpDown) {
|
||||
_drawSurface.blitFrom(_image, _horizontalWallSrc, dest + Common::Point(0, cellRect.height() - 1));
|
||||
}
|
||||
|
||||
if (cell == kWallLeft || cell == kWallLeftRight) {
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest - Common::Point(_lineWidth, 0));
|
||||
}
|
||||
|
||||
if (cell == kWallRight || cell == kWallLeftRight) {
|
||||
_drawSurface.blitFrom(_image, _verticalWallSrc, dest + Common::Point(cellRect.width() - 1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::enemyMovement(uint enemyID) {
|
||||
if (enemyID >= _pieces.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Piece &player = _pieces[0];
|
||||
Piece &enemy = _pieces[enemyID];
|
||||
Common::Point diff = player._gridPos - enemy._gridPos;
|
||||
|
||||
// First, try to move vertically
|
||||
if (diff.y) {
|
||||
if (diff.y > 0) {
|
||||
// Player is lower than enemy, try to move down
|
||||
if (canMove(enemyID, kWallDown)) {
|
||||
++enemy._gridPos.y;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Player is higher than enemy, try to move up
|
||||
if (canMove(enemyID, kWallUp)) {
|
||||
--enemy._gridPos.y;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then, try to move horizontally. Note that when the player is on the same row,
|
||||
// the enemy will not move if adjacent to a wall; this is intentional
|
||||
if (diff.x) {
|
||||
if (diff.x > 0) {
|
||||
// Player is to the enemy's right
|
||||
if (canMove(enemyID, kWallRight)) {
|
||||
++enemy._gridPos.x;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Player is to the enemy's left
|
||||
if (canMove(enemyID, kWallLeft)) {
|
||||
--enemy._gridPos.x;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MazeChasePuzzle::canMove(uint pieceID, WallType direction) {
|
||||
Piece &piece = _pieces[pieceID];
|
||||
switch (direction) {
|
||||
case kWallLeft :
|
||||
if ( piece._gridPos.x == 0 ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x - 1] == kWallRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x - 1] == kWallLeftRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallLeft ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallLeftRight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(-1, 0) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
case kWallRight :
|
||||
if ( piece._gridPos.x == (int)_grid[0].size() - 1 ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x + 1] == kWallLeft ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x + 1] == kWallLeftRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallRight ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallLeftRight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(1, 0) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
case kWallUp :
|
||||
if ( piece._gridPos.y == 0 ||
|
||||
_grid[piece._gridPos.y - 1][piece._gridPos.x] == kWallDown ||
|
||||
_grid[piece._gridPos.y - 1][piece._gridPos.x] == kWallUpDown ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallUp ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallUpDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(0, -1) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
case kWallDown :
|
||||
if ( piece._gridPos.y == (int)_grid.size() - 1 ||
|
||||
_grid[piece._gridPos.y + 1][piece._gridPos.x] == kWallUp ||
|
||||
_grid[piece._gridPos.y + 1][piece._gridPos.x] == kWallUpDown ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallDown ||
|
||||
_grid[piece._gridPos.y][piece._gridPos.x] == kWallUpDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pieceID != 0) {
|
||||
for (uint i = 1; i < _pieces.size(); ++i) {
|
||||
if (piece._gridPos + Common::Point(0, 1) == _pieces[i]._gridPos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
default :
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void MazeChasePuzzle::reset() {
|
||||
for (uint i = 0; i < _pieces.size(); ++i) {
|
||||
_pieces[i]._gridPos = _pieces[i]._lastPos = _startLocations[i];
|
||||
_pieces[i].moveTo(getScreenPosition(_pieces[i]._gridPos));
|
||||
}
|
||||
|
||||
Common::Rect fill = _upButtonDest;
|
||||
fill.extend(_downButtonDest);
|
||||
fill.extend(_leftButtonDest);
|
||||
fill.extend(_rightButtonDest);
|
||||
fill.extend(_resetButtonDest);
|
||||
_drawSurface.fillRect(fill, _drawSurface.getTransparentColor());
|
||||
|
||||
_currentAnimFrame = -1;
|
||||
_reset = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
127
engines/nancy/action/puzzle/mazechasepuzzle.h
Normal file
127
engines/nancy/action/puzzle/mazechasepuzzle.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
#define NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Implements a puzzle introduced in nancy5 where the player controls
|
||||
// one piece being chased by several other pieces on a grid. Movement
|
||||
// is performed via buttons, and both player and enemy navigate one
|
||||
// tile at a time. Has some similarities to CollisionPuzzle, but was
|
||||
// different enough to warrant its own class.
|
||||
class MazeChasePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MazeChasePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MazeChasePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
enum WallType { kWallLeft = 1, kWallUp = 2, kWallRight = 3, kWallDown = 4, kWallLeftRight = 6, kWallUpDown = 6 };
|
||||
|
||||
class Piece : public RenderObject {
|
||||
public:
|
||||
Piece(uint z) : RenderObject(z) {}
|
||||
virtual ~Piece() {}
|
||||
|
||||
Common::Point _gridPos;
|
||||
Common::Point _lastPos;
|
||||
|
||||
protected:
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
Common::String getRecordTypeName() const override { return "MazeChasePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Rect getScreenPosition(Common::Point gridPos);
|
||||
void drawGrid();
|
||||
void enemyMovement(uint enemyID);
|
||||
bool canMove(uint pieceID, WallType direction);
|
||||
void reset();
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Point _exitPos = Common::Point(-1, -1);
|
||||
|
||||
Common::Array<Common::Array<uint16>> _grid;
|
||||
Common::Array<Common::Point> _startLocations;
|
||||
|
||||
Common::Rect _playerSrc;
|
||||
Common::Rect _enemySrc;
|
||||
Common::Rect _verticalWallSrc;
|
||||
Common::Rect _horizontalWallSrc;
|
||||
Common::Rect _lightSrc;
|
||||
|
||||
Common::Rect _upButtonSrc;
|
||||
Common::Rect _rightButtonSrc;
|
||||
Common::Rect _downButtonSrc;
|
||||
Common::Rect _leftButtonSrc;
|
||||
Common::Rect _resetButtonSrc;
|
||||
|
||||
Common::Point _gridPos;
|
||||
|
||||
Common::Rect _lightDest;
|
||||
|
||||
Common::Rect _upButtonDest;
|
||||
Common::Rect _rightButtonDest;
|
||||
Common::Rect _downButtonDest;
|
||||
Common::Rect _leftButtonDest;
|
||||
Common::Rect _resetButtonDest;
|
||||
|
||||
uint16 _lineWidth = 0;
|
||||
uint16 _framesPerMove = 0;
|
||||
|
||||
SoundDescription _failSound;
|
||||
SoundDescription _moveSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<Piece> _pieces;
|
||||
|
||||
int _currentAnimFrame = -1;
|
||||
|
||||
uint32 _solveSoundPlayTime = 0;
|
||||
bool _solved = false;
|
||||
bool _reset = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/memorypuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/memorypuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/memorypuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MemoryPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void MemoryPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Memory puzzle");
|
||||
NancySceneState.setEventFlag(423, g_nancy->_true); // EV_Solved_Necklace_Box
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 3846;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void MemoryPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void MemoryPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/memorypuzzle.h
Normal file
51
engines/nancy/action/puzzle/memorypuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MEMORYPUZZLE_H
|
||||
#define NANCY_ACTION_MEMORYPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Memory puzzle (toy box) in Nancy 9
|
||||
|
||||
class MemoryPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MemoryPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MemoryPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MemoryPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MEMORYPUZZLE_H
|
||||
120
engines/nancy/action/puzzle/mouselightpuzzle.cpp
Normal file
120
engines/nancy/action/puzzle/mouselightpuzzle.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/mouselightpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MouseLightPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
|
||||
Graphics::ManagedSurface baseImage;
|
||||
g_nancy->_resource->loadImage(_imageName, baseImage);
|
||||
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getTransparentPixelFormat());
|
||||
_drawSurface.blitFrom(baseImage);
|
||||
((Graphics::Surface)_drawSurface).setAlpha(0);
|
||||
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
_maskCircle.create(_radius * 2, _radius * 2, g_nancy->_graphics->getInputPixelFormat());
|
||||
_maskCircle.clear();
|
||||
|
||||
if (_smoothEdges) {
|
||||
for (int y = -_radius; y < _radius; ++y) {
|
||||
for (int x = -_radius; x < _radius; ++x) {
|
||||
_maskCircle.setPixel(x + _radius, y + _radius, (uint16)(expf(-(float)((y * y + x * x) * (y * y + x * x)) / (float)(_radius * _radius * _radius * _radius / 4)) * 0xFF));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int y = -_radius; y < _radius; ++y) {
|
||||
for (int x = -_radius; x < _radius; ++x) {
|
||||
if (sqrt(y * y + x * x) < _radius) {
|
||||
_maskCircle.setPixel(x + _radius, y + _radius, 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MouseLightPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseLightPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
_radius = stream.readByte();
|
||||
_smoothEdges = stream.readByte();
|
||||
}
|
||||
|
||||
void MouseLightPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastMousePos == input.mousePos) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastMousePos = input.mousePos;
|
||||
((Graphics::Surface)_drawSurface).setAlpha(0);
|
||||
_needsRedraw = true;
|
||||
|
||||
Common::Rect vpScreenPos = NancySceneState.getViewport().convertViewportToScreen(_screenPosition);
|
||||
if (!vpScreenPos.contains(input.mousePos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point blitDestPoint = input.mousePos;
|
||||
blitDestPoint -= { vpScreenPos.left, vpScreenPos.top };
|
||||
blitDestPoint -= { _radius, _radius };
|
||||
|
||||
Common::Rect srcRect = _maskCircle.getBounds();
|
||||
Common::Rect::getBlitRect(blitDestPoint, srcRect, _drawSurface.getBounds());
|
||||
|
||||
// Copy over the transparency to the draw surface
|
||||
for (int y = srcRect.top; y < srcRect.bottom; ++y) {
|
||||
uint32 *drawSurfPtr = (uint32 *)_drawSurface.getBasePtr(blitDestPoint.x, y + blitDestPoint.y - srcRect.top);
|
||||
uint16 *circlePtr = (uint16 *)_maskCircle.getBasePtr(srcRect.left, y);
|
||||
for (int x = srcRect.left; x < srcRect.right; ++x) {
|
||||
*drawSurfPtr = (*drawSurfPtr & 0xFFFFFF00) | (byte)*circlePtr;
|
||||
++drawSurfPtr;
|
||||
++circlePtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
63
engines/nancy/action/puzzle/mouselightpuzzle.h
Normal file
63
engines/nancy/action/puzzle/mouselightpuzzle.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MOUSELIGHTPUZZLE_H
|
||||
#define NANCY_ACTION_MOUSELIGHTPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Shows a single image over the entire frame, with most of it blackened;
|
||||
// a circle around that follows the cursor reveals parts of the image.
|
||||
// Circle can have smooth or hard edges. Not actually a puzzle.
|
||||
|
||||
// TODO: Optimize blitting; currently, the whole screen is redrawn
|
||||
// TODO: Add noise to the circle mask; there are artifacts at low brightness
|
||||
class MouseLightPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MouseLightPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MouseLightPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MouseLightPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
byte _radius = 0;
|
||||
bool _smoothEdges = false;
|
||||
|
||||
Graphics::ManagedSurface _maskCircle;
|
||||
|
||||
Common::Point _lastMousePos;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_MAZECHASEPUZZLE_H
|
||||
87
engines/nancy/action/puzzle/multibuildpuzzle.cpp
Normal file
87
engines/nancy/action/puzzle/multibuildpuzzle.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/multibuildpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void MultiBuildPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void MultiBuildPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
|
||||
SceneChangeDescription scene;
|
||||
uint16 sceneID = NancySceneState.getSceneInfo().sceneID;
|
||||
|
||||
switch (sceneID) {
|
||||
case 2025:
|
||||
warning("STUB - Nancy 9 Sand castle puzzle");
|
||||
scene.sceneID = 2024;
|
||||
break;
|
||||
case 2575:
|
||||
warning("STUB - Nancy 9 Sandwich making puzzle");
|
||||
NancySceneState.setEventFlag(428, g_nancy->_true); // EV_Solved_Sandwich_Bad
|
||||
NancySceneState.setEventFlag(429, g_nancy->_true); // EV_Solved_Sandwich_Good
|
||||
scene.sceneID = 2572;
|
||||
break;
|
||||
case 2585:
|
||||
warning("STUB - Nancy 9 Book sorting puzzle");
|
||||
NancySceneState.setEventFlag(397, g_nancy->_true); // Set puzzle flag to solved
|
||||
scene.sceneID = 2583;
|
||||
break;
|
||||
default:
|
||||
warning("MultiBuildPuzzle: Unknown scene %d", sceneID);
|
||||
return;
|
||||
}
|
||||
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void MultiBuildPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void MultiBuildPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/multibuildpuzzle.h
Normal file
51
engines/nancy/action/puzzle/multibuildpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_MULTIBUILDPUZZLE_H
|
||||
#define NANCY_ACTION_MULTIBUILDPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Puzzle in Nancy 9, where an item is built from smaller pieces
|
||||
|
||||
class MultiBuildPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
MultiBuildPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~MultiBuildPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "MultiBuildPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
79
engines/nancy/action/puzzle/onebuildpuzzle.cpp
Normal file
79
engines/nancy/action/puzzle/onebuildpuzzle.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/onebuildpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void OneBuildPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void OneBuildPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
const uint16 sceneId = NancySceneState.getSceneInfo().sceneID;
|
||||
SceneChangeDescription scene;
|
||||
|
||||
if (sceneId == 6519) {
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 9 Pipe joining puzzle under sink");
|
||||
NancySceneState.setEventFlag(425, g_nancy->_true); // EV_Solved_Pipes
|
||||
scene.sceneID = 6520;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
} else if (sceneId == 2916) {
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 9 Carborosaurus Puzzle");
|
||||
NancySceneState.setEventFlag(424, g_nancy->_true); // EV_Solved_Permit_Task
|
||||
scene.sceneID = 2915;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
} else {
|
||||
warning("STUB - Nancy 9 One Build Puzzle");
|
||||
}
|
||||
}
|
||||
|
||||
void OneBuildPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void OneBuildPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/onebuildpuzzle.h
Normal file
51
engines/nancy/action/puzzle/onebuildpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
#define NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Pipe joining puzzle under sink in Nancy 9
|
||||
|
||||
class OneBuildPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
OneBuildPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~OneBuildPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "OneBuildPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ONEBUILDPUZZLE_H
|
||||
634
engines/nancy/action/puzzle/orderingpuzzle.cpp
Normal file
634
engines/nancy/action/puzzle/orderingpuzzle.cpp
Normal file
@@ -0,0 +1,634 @@
|
||||
/* 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 "common/serializer.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/orderingpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void OrderingPuzzle::init() {
|
||||
for (uint i = 0; i < _destRects.size(); ++i) {
|
||||
if (i == 0) {
|
||||
_screenPosition = _destRects[i];
|
||||
} else {
|
||||
_screenPosition.extend(_destRects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _overlayDests.size(); ++i) {
|
||||
_screenPosition.extend(_overlayDests[i]);
|
||||
}
|
||||
|
||||
if (!_checkButtonDest.isEmpty()) {
|
||||
_screenPosition.extend(_checkButtonDest);
|
||||
}
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
|
||||
if (_image.hasPalette()) {
|
||||
uint8 palette[256 * 3];
|
||||
_image.grabPalette(palette, 0, 256);
|
||||
_drawSurface.setPalette(palette, 0, 256);
|
||||
}
|
||||
|
||||
setTransparent(true);
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
setVisible(true);
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
bool isPiano = _puzzleType == kPiano;
|
||||
bool isOrderItems = _puzzleType == kOrderItems;
|
||||
bool isKeypad = _puzzleType == kKeypad || _puzzleType == kKeypadTerse;
|
||||
readFilename(stream, _imageName);
|
||||
Common::Serializer ser(&stream, nullptr);
|
||||
ser.setVersion(g_nancy->getGameType());
|
||||
|
||||
uint16 numElements = 5;
|
||||
uint16 maxNumElements = 15;
|
||||
if (ser.getVersion() == kGameTypeVampire) {
|
||||
// Hardcoded in The Vampire Diaries
|
||||
numElements = maxNumElements = 5;
|
||||
} else {
|
||||
ser.syncAsUint16LE(numElements);
|
||||
}
|
||||
|
||||
switch (_puzzleType) {
|
||||
case kOrderItems :
|
||||
ser.syncAsByte(_hasSecondState);
|
||||
ser.syncAsByte(_itemsStayDown);
|
||||
break;
|
||||
case kPiano :
|
||||
_itemsStayDown = false;
|
||||
break;
|
||||
case kKeypadTerse:
|
||||
// fall through
|
||||
case kKeypad :
|
||||
ser.syncAsByte(_itemsStayDown);
|
||||
ser.syncAsByte(_needButtonToCheckSuccess);
|
||||
readRect(ser, _checkButtonSrc);
|
||||
readRect(ser, _checkButtonDest);
|
||||
maxNumElements = 30;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// nancy7 moved the keypad rects at the end
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy6 || !isKeypad) {
|
||||
readRectArray(ser, _down1Rects, numElements, maxNumElements);
|
||||
|
||||
if (isOrderItems) {
|
||||
readRectArray(stream, _up2Rects, numElements, maxNumElements);
|
||||
readRectArray(stream, _down2Rects, numElements, maxNumElements);
|
||||
}
|
||||
|
||||
readRectArray(ser, _destRects, numElements, maxNumElements);
|
||||
|
||||
if (isPiano) {
|
||||
readRectArray(stream, _hotspots, numElements, maxNumElements);
|
||||
} else {
|
||||
_hotspots = _destRects;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPiano && g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
_specialCursor1Id = stream.readUint16LE();
|
||||
readRect(stream, _specialCursor1Dest);
|
||||
_specialCursor2Id = stream.readUint16LE();
|
||||
readRect(stream, _specialCursor2Dest);
|
||||
}
|
||||
|
||||
uint sequenceLength = 5;
|
||||
ser.syncAsUint16LE(sequenceLength, kGameTypeNancy1);
|
||||
|
||||
if (isKeypad) {
|
||||
ser.syncAsByte(_checkOrder, kGameTypeNancy7);
|
||||
}
|
||||
|
||||
_correctSequence.resize(sequenceLength);
|
||||
uint sizeElem = 1;
|
||||
for (uint i = 0; i < sequenceLength; ++i) {
|
||||
switch (_puzzleType) {
|
||||
case kKeypadTerse:
|
||||
// fall through
|
||||
case kKeypad :
|
||||
// fall through
|
||||
case kOrdering:
|
||||
ser.syncAsByte(_correctSequence[i]);
|
||||
sizeElem = 1;
|
||||
break;
|
||||
case kPiano:
|
||||
ser.syncAsUint16LE(_correctSequence[i]);
|
||||
sizeElem = 2;
|
||||
break;
|
||||
case kOrderItems:
|
||||
// For some reason, OrderItems labels starting from 1
|
||||
ser.syncAsUint16LE(_correctSequence[i]);
|
||||
--_correctSequence[i];
|
||||
sizeElem = 2;
|
||||
break;
|
||||
default:
|
||||
error("OrderingPuzzle::readData(): Unsupported puzzle type %d", _puzzleType);
|
||||
}
|
||||
}
|
||||
ser.skip((maxNumElements - sequenceLength) * sizeElem, kGameTypeNancy1);
|
||||
|
||||
if (isOrderItems) {
|
||||
uint numOverlays = 0;
|
||||
ser.syncAsUint16LE(_state2InvItem);
|
||||
ser.syncAsUint16LE(numOverlays);
|
||||
|
||||
readRectArray(ser, _overlaySrcs, numOverlays);
|
||||
readRectArray(ser, _overlayDests, numOverlays);
|
||||
} else if (isPiano && g_nancy->getGameType() >= kGameTypeNancy8) {
|
||||
readFilenameArray(stream, _pianoSoundNames, numElements);
|
||||
stream.skip((maxNumElements - numElements) * 33);
|
||||
}
|
||||
|
||||
if (ser.getVersion() > kGameTypeVampire) {
|
||||
_pushDownSound.readNormal(stream);
|
||||
|
||||
if (isOrderItems) {
|
||||
_itemSound.readNormal(stream);
|
||||
_popUpSound.readNormal(stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (ser.getVersion() == kGameTypeVampire) {
|
||||
_solveExitScene._sceneChange.readData(stream, true);
|
||||
ser.skip(2); // shouldStopRendering
|
||||
ser.syncAsSint16LE(_solveExitScene._flag.label);
|
||||
ser.syncAsByte(_solveExitScene._flag.flag);
|
||||
} else {
|
||||
_solveExitScene.readData(stream);
|
||||
}
|
||||
|
||||
ser.syncAsUint16LE(_solveSoundDelay);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
if (ser.getVersion() == kGameTypeVampire) {
|
||||
_exitScene._sceneChange.readData(stream, true);
|
||||
ser.skip(2); // shouldStopRendering
|
||||
ser.syncAsSint16LE(_exitScene._flag.label);
|
||||
ser.syncAsByte(_exitScene._flag.flag);
|
||||
} else {
|
||||
_exitScene.readData(stream);
|
||||
}
|
||||
|
||||
readRect(stream, _exitHotspot);
|
||||
|
||||
if (isKeypad && g_nancy->getGameType() >= kGameTypeNancy7) {
|
||||
if (_puzzleType == kKeypad) {
|
||||
readRectArray(ser, _down1Rects, numElements, maxNumElements);
|
||||
readRectArray(ser, _destRects, numElements, maxNumElements);
|
||||
} else if (_puzzleType == kKeypadTerse) {
|
||||
_down1Rects.resize(numElements);
|
||||
_destRects.resize(numElements);
|
||||
|
||||
// Terse elements are the same size & placed on a grid (in the source image AND on screen)
|
||||
uint16 columns = stream.readUint16LE();
|
||||
stream.skip(2); // rows
|
||||
|
||||
uint16 width = stream.readUint16LE();
|
||||
uint16 height = stream.readUint16LE();
|
||||
|
||||
Common::Point srcStartPos, srcDist, destStartPos, destDist;
|
||||
|
||||
srcStartPos.x = stream.readUint16LE();
|
||||
srcStartPos.y = stream.readUint16LE();
|
||||
srcDist.x = stream.readUint16LE();
|
||||
srcDist.y = stream.readUint16LE();
|
||||
|
||||
destStartPos.x = stream.readUint16LE();
|
||||
destStartPos.y = stream.readUint16LE();
|
||||
destDist.x = stream.readUint16LE();
|
||||
destDist.y = stream.readUint16LE();
|
||||
|
||||
for (uint i = 0; i < numElements; ++i) {
|
||||
uint x = i % columns;
|
||||
uint y = i / columns;
|
||||
Common::Rect &src = _down1Rects[i];
|
||||
src.left = srcStartPos.x + (x * srcDist.x) + (width * x);
|
||||
src.top = srcStartPos.y + (y * srcDist.y) + (height * y);
|
||||
src.setWidth(width + 1);
|
||||
src.setHeight(height + 1);
|
||||
|
||||
Common::Rect &dest = _destRects[i];
|
||||
dest.left = destStartPos.x + (x * destDist.x) + (width * x);
|
||||
dest.top = destStartPos.y + (y * destDist.y) + (height * y);
|
||||
dest.setWidth(width + 1);
|
||||
dest.setHeight(height + 1);
|
||||
}
|
||||
}
|
||||
|
||||
_hotspots = _destRects;
|
||||
}
|
||||
|
||||
_downItems.resize(numElements, false);
|
||||
_secondStateItems.resize(numElements, false);
|
||||
}
|
||||
|
||||
void OrderingPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
if (g_nancy->getGameType() > kGameTypeVampire) {
|
||||
g_nancy->_sound->loadSound(_pushDownSound);
|
||||
if (_puzzleType == kOrderItems) {
|
||||
g_nancy->_sound->loadSound(_itemSound);
|
||||
g_nancy->_sound->loadSound(_popUpSound);
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved: {
|
||||
if (!_itemsStayDown) {
|
||||
// Clear the pushed item
|
||||
if (g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _downItems.size(); ++i) {
|
||||
if (_downItems[i]) {
|
||||
popUp(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool solved = true;
|
||||
|
||||
if (_puzzleType != kPiano) {
|
||||
if (_clickedSequence.size() >= _correctSequence.size()) {
|
||||
bool equal = true;
|
||||
if (_checkOrder) {
|
||||
equal = (_clickedSequence == _correctSequence);
|
||||
} else {
|
||||
for (uint i = 0; i < _correctSequence.size(); ++i) {
|
||||
bool found = false;
|
||||
for (uint j = 0; j < _clickedSequence.size(); ++j) {
|
||||
if (_correctSequence[i] == _clickedSequence[j]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Couldn't find one of the items in the correct sequence
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the pressed sequence. If its length is above a certain number,
|
||||
// clear it and start anew
|
||||
if (!equal) {
|
||||
if (_puzzleType != kOrderItems) {
|
||||
uint maxNumPressed = 4;
|
||||
if (g_nancy->getGameType() > kGameTypeVampire) {
|
||||
if (_puzzleType == kKeypad || _puzzleType == kKeypadTerse) {
|
||||
maxNumPressed = _correctSequence.size();
|
||||
} else {
|
||||
maxNumPressed = _correctSequence.size() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_clickedSequence.size() > maxNumPressed) {
|
||||
clearAllElements();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// OrderItems has a slight delay, after which it actually clears
|
||||
if (_clickedSequence.size() == _correctSequence.size()) {
|
||||
if (_solveSoundPlayTime == 0) {
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + 500;
|
||||
} else {
|
||||
if (g_nancy->getTotalPlayTime() > _solveSoundPlayTime) {
|
||||
clearAllElements();
|
||||
_solveSoundPlayTime = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
solved = false;
|
||||
}
|
||||
} else {
|
||||
solved = false;
|
||||
}
|
||||
} else {
|
||||
// Piano puzzle checks only the last few elements
|
||||
if (_clickedSequence.size() < _correctSequence.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Arbitrary number
|
||||
if (_clickedSequence.size() > 30) {
|
||||
_clickedSequence.erase(&_clickedSequence[0], &_clickedSequence[_clickedSequence.size() - _correctSequence.size()]);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _correctSequence.size(); ++i) {
|
||||
if (_clickedSequence[_clickedSequence.size() - _correctSequence.size() + i] != (int16)_correctSequence[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kKeypad && _needButtonToCheckSuccess) {
|
||||
// KeypadPuzzle moves to the "success" scene regardless whether the puzzle was solved or not,
|
||||
// provided the check button is pressed.
|
||||
if (_checkButtonPressed) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
if (solved) {
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (solved) {
|
||||
if (_puzzleType == kOrderItems) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
// Draw some overlays when solved correctly (OrderItems only)
|
||||
for (uint i = 0; i < _overlaySrcs.size(); ++i) {
|
||||
Common::Rect destRect = _overlayDests[i];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
_drawSurface.blitFrom(_image, _overlaySrcs[i], destRect);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_solveState = kPlaySound;
|
||||
}
|
||||
// fall through
|
||||
case kPlaySound:
|
||||
if (g_nancy->getTotalPlayTime() <= _solveSoundPlayTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
g_nancy->_sound->stopSound("BUOK");
|
||||
} else {
|
||||
g_nancy->_sound->stopSound(_pushDownSound);
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
_exitScene.execute();
|
||||
} else {
|
||||
NancySceneState.changeScene(_solveExitScene._sceneChange);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OrderingPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool canClick = true;
|
||||
if ((_itemsStayDown || _puzzleType == kPiano) && g_nancy->_sound->isSoundPlaying(_pushDownSound)) {
|
||||
canClick = false;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_needButtonToCheckSuccess && NancySceneState.getViewport().convertViewportToScreen(_checkButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_checkButtonPressed = true;
|
||||
g_nancy->_sound->playSound(_pushDownSound);
|
||||
Common::Rect destRect = _checkButtonDest;
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
_drawSurface.blitFrom(_image, _checkButtonSrc, destRect);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)_hotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
// Set the custom cursor for nancy8+ PianoPuzzle
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_specialCursor1Dest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType((CursorManager::CursorType)_specialCursor1Id);
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_specialCursor2Dest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType((CursorManager::CursorType)_specialCursor2Id);
|
||||
} else {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
}
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (_puzzleType == kOrderItems) {
|
||||
if (_itemsStayDown && _downItems[i]) {
|
||||
// Button is pressed, OrderItems does not allow for depressing
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getHeldItem() == _state2InvItem) {
|
||||
// We are holding the correct inventory, set the button to its alternate (dusted) state
|
||||
setToSecondState(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_puzzleType == kPiano) {
|
||||
// Set the correct sound name for every piano key
|
||||
if (g_nancy->getGameType() <= kGameTypeNancy7) {
|
||||
// In earlier games, the sound name is the base sound + a number
|
||||
if (Common::isDigit(_pushDownSound.name.lastChar())) {
|
||||
_pushDownSound.name.deleteLastChar();
|
||||
}
|
||||
|
||||
_pushDownSound.name.insertChar('0' + i, _pushDownSound.name.size());
|
||||
} else {
|
||||
// Later games added an array of sound names
|
||||
_pushDownSound.name = _pianoSoundNames[i];
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_pushDownSound);
|
||||
}
|
||||
|
||||
if (_puzzleType == kOrdering || _puzzleType == kKeypad || _puzzleType == kKeypadTerse) {
|
||||
// OrderingPuzzle and KeypadPuzzle allow for depressing buttons after they're pressed.
|
||||
// If the button is the last one the player pressed, it is removed from the order.
|
||||
// If not, the sequence is kept wrong and will be reset after enough buttons are pressed
|
||||
for (uint j = 0; j < _clickedSequence.size(); ++j) {
|
||||
if (_clickedSequence[j] == i && _downItems[i] == true) {
|
||||
popUp(i);
|
||||
if (_clickedSequence.back() == i) {
|
||||
_clickedSequence.pop_back();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_clickedSequence.push_back(i);
|
||||
pushDown(i);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::String OrderingPuzzle::getRecordTypeName() const {
|
||||
switch (_puzzleType) {
|
||||
case kPiano:
|
||||
return "PianoPuzzle";
|
||||
case kOrderItems:
|
||||
return "OrderItemsPuzzle";
|
||||
case kKeypad:
|
||||
return "KeypadPuzzle";
|
||||
case kKeypadTerse:
|
||||
return "KeypadTersePuzzle";
|
||||
default:
|
||||
return "OrderingPuzzle";
|
||||
}
|
||||
}
|
||||
|
||||
void OrderingPuzzle::pushDown(uint id) {
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
g_nancy->_sound->playSound("BUOK");
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_pushDownSound);
|
||||
}
|
||||
|
||||
_downItems[id] = true;
|
||||
Common::Rect destRect = _destRects[id];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _secondStateItems[id] ? _down2Rects[id] : _down1Rects[id], destRect);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OrderingPuzzle::setToSecondState(uint id) {
|
||||
g_nancy->_sound->playSound(_itemSound);
|
||||
|
||||
_secondStateItems[id] = true;
|
||||
Common::Rect destRect = _destRects[id];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _downItems[id] ? _down2Rects[id] : _up2Rects[id], destRect);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OrderingPuzzle::popUp(uint id) {
|
||||
if (_itemsStayDown) {
|
||||
// Make sure we only play the sound when the buttons don't auto-depress
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
g_nancy->_sound->playSound("BUOK");
|
||||
} else {
|
||||
if (_popUpSound.name.size()) {
|
||||
g_nancy->_sound->playSound(_popUpSound);
|
||||
} else {
|
||||
g_nancy->_sound->playSound(_pushDownSound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_downItems[id] = false;
|
||||
Common::Rect destRect = _destRects[id];
|
||||
destRect.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
|
||||
if (_secondStateItems[id] == false || _up2Rects.size() == 0) {
|
||||
_drawSurface.fillRect(destRect, _drawSurface.getTransparentColor());
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image, _up2Rects[id], destRect);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OrderingPuzzle::clearAllElements() {
|
||||
for (uint id = 0; id < _downItems.size(); ++id) {
|
||||
popUp(id);
|
||||
}
|
||||
|
||||
_clickedSequence.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
110
engines/nancy/action/puzzle/orderingpuzzle.h
Normal file
110
engines/nancy/action/puzzle/orderingpuzzle.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ORDERINGPUZZLE_H
|
||||
#define NANCY_ACTION_ORDERINGPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Class implementing several action records of the type where
|
||||
// the player has to press a sequence of buttons in a certain order.
|
||||
// - OrderingPuzzle: The most simple type. Allows for manual depressing of buttons
|
||||
// - PianoPuzzle: Buttons always auto-depress; every button has unique sound file
|
||||
// - OrderItemsPuzzle: Buttons may depress or stay down, but player can't depress manually.
|
||||
// Has second button state that is activated when player is holding a specific item. (see fingerprint keypad puzzle in nancy4)
|
||||
// - KeypadPuzzle: Buttons may auto-depress, stay down, and can be depressed manually by player.
|
||||
// Adds an optional button for manually checking for correct solution, number of possible buttons is 30.
|
||||
// - KeypadPuzzleTerse: Same as above, but data format is shorter, and supports up to 100 buttons
|
||||
class OrderingPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
|
||||
enum PuzzleType { kOrdering, kPiano, kOrderItems, kKeypad, kKeypadTerse };
|
||||
OrderingPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
|
||||
virtual ~OrderingPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override;
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void pushDown(uint id);
|
||||
void setToSecondState(uint id);
|
||||
void popUp(uint id);
|
||||
void clearAllElements();
|
||||
|
||||
Common::Path _imageName;
|
||||
bool _hasSecondState = false;
|
||||
bool _itemsStayDown = true;
|
||||
bool _needButtonToCheckSuccess = false;
|
||||
bool _checkOrder = true;
|
||||
Common::Rect _checkButtonSrc;
|
||||
Common::Rect _checkButtonDest;
|
||||
Common::Array<Common::Rect> _down1Rects;
|
||||
Common::Array<Common::Rect> _up2Rects;
|
||||
Common::Array<Common::Rect> _down2Rects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<uint16> _correctSequence;
|
||||
|
||||
uint16 _specialCursor1Id = CursorManager::kHotspot;
|
||||
Common::Rect _specialCursor1Dest;
|
||||
uint16 _specialCursor2Id = CursorManager::kHotspot;
|
||||
Common::Rect _specialCursor2Dest;
|
||||
|
||||
Common::Array<Common::String> _pianoSoundNames; // nancy8 and up
|
||||
|
||||
uint16 _state2InvItem = 0;
|
||||
Common::Array<Common::Rect> _overlaySrcs;
|
||||
Common::Array<Common::Rect> _overlayDests;
|
||||
|
||||
Nancy::SoundDescription _pushDownSound;
|
||||
Nancy::SoundDescription _itemSound;
|
||||
Nancy::SoundDescription _popUpSound;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
Nancy::SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<uint16> _clickedSequence;
|
||||
Common::Array<bool> _downItems;
|
||||
Common::Array<bool> _secondStateItems;
|
||||
Time _solveSoundPlayTime;
|
||||
bool _checkButtonPressed = false;
|
||||
|
||||
PuzzleType _puzzleType;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ORDERINGPUZZLE_H
|
||||
236
engines/nancy/action/puzzle/overridelockpuzzle.cpp
Normal file
236
engines/nancy/action/puzzle/overridelockpuzzle.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/ui/viewport.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/overridelockpuzzle.h"
|
||||
|
||||
#include "common/random.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void OverrideLockPuzzle::init() {
|
||||
Common::Rect bounds = NancySceneState.getViewport().getBounds();
|
||||
|
||||
_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(bounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
byte num = stream.readByte();
|
||||
_popButtons = stream.readByte();
|
||||
_randomizeLights = stream.readByte();
|
||||
|
||||
readRectArray(stream, _buttonSrcs, num, 10);
|
||||
readRectArray(stream, _buttonDests, num, 10);
|
||||
readRectArray(stream, _hotspots, num, 10);
|
||||
readRectArray(stream, _lightSrcs, num, 10);
|
||||
readRectArray(stream, _lightDests, num, 10);
|
||||
|
||||
_buttonSound.readNormal(stream);
|
||||
_wrongSound.readNormal(stream);
|
||||
|
||||
_buttonPopTime = stream.readUint16LE();
|
||||
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
if (g_nancy->getGameType() != kGameTypeNancy5 || NancySceneState.getHeldItem() != 12) {
|
||||
// Hardcoded check for rubber gloves in nancy5
|
||||
NancySceneState.setNoHeldItem();
|
||||
}
|
||||
|
||||
// Set the order of the button presses (always random)
|
||||
// and of the lights (only random on expert difficulty)
|
||||
uint numButtons = _buttonSrcs.size();
|
||||
_buttonOrder.resize(numButtons);
|
||||
_lightsOrder.resize(numButtons);
|
||||
Common::Array<byte> buttonIDs(numButtons);
|
||||
Common::Array<byte> lightIDs(numButtons);
|
||||
for (uint i = 0; i < numButtons; ++i) {
|
||||
buttonIDs[i] = i;
|
||||
lightIDs[i] = i;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < numButtons; ++i) {
|
||||
_buttonOrder[i] = buttonIDs.remove_at(g_nancy->_randomSource->getRandomNumber(buttonIDs.size() - 1));
|
||||
|
||||
if (_randomizeLights == kLightsRandom) {
|
||||
_lightsOrder[i] = lightIDs.remove_at(g_nancy->_randomSource->getRandomNumber(lightIDs.size() - 1));
|
||||
} else {
|
||||
_lightsOrder[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_buttonSound);
|
||||
g_nancy->_sound->loadSound(_wrongSound);
|
||||
_state = kRun;
|
||||
}
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_timeToPop != 0 && g_nancy->getTotalPlayTime() > _timeToPop) {
|
||||
if (_popButtons == kButtonsPopUp) {
|
||||
drawButton(_lastPushedButton, true);
|
||||
}
|
||||
|
||||
drawLights();
|
||||
|
||||
_lastPushedButton = -1;
|
||||
_timeToPop = 0;
|
||||
|
||||
for (uint i = 0; i < _playerOrder.size(); ++i) {
|
||||
if (_playerOrder[i] != _buttonOrder[i]) {
|
||||
// Wrong order, reset
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
_playerOrder.clear();
|
||||
_needsRedraw = true;
|
||||
g_nancy->_sound->playSound(_wrongSound);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_playerOrder.size() == _buttonOrder.size()) {
|
||||
// Solved the puzzle
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_state = kActionTrigger;
|
||||
_solveState = kSolved;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
_exitScene.execute();
|
||||
break;
|
||||
case kSolved:
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_solveExitScene.execute();
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
g_nancy->_sound->stopSound(_wrongSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::handleInput(NancyInput &input) {
|
||||
if ((_state != kRun && _solveState != kNotSolved) || _timeToPop != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the exit hotspot
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _buttonOrder.size(); ++i) {
|
||||
bool hotspotIsInactive = false;
|
||||
for (uint j = 0; j < _playerOrder.size(); ++j) {
|
||||
if (_playerOrder[j] == i) {
|
||||
hotspotIsInactive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hotspotIsInactive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_buttonSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
drawButton(i, false);
|
||||
_lastPushedButton = i;
|
||||
_timeToPop = g_nancy->getTotalPlayTime() + _buttonPopTime;
|
||||
_playerOrder.push_back(i);
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::drawButton(uint buttonID, bool clear) {
|
||||
if (clear) {
|
||||
_drawSurface.fillRect(_buttonDests[buttonID], _drawSurface.getTransparentColor());
|
||||
return;
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image, _buttonSrcs[buttonID], _buttonDests[buttonID]);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void OverrideLockPuzzle::drawLights() {
|
||||
for (uint i = 0; i < _playerOrder.size(); ++i) {
|
||||
if (_randomizeLights == kLightsCircular) {
|
||||
_drawSurface.blitFrom(_image, _lightSrcs[i], _lightDests[i]);
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image, _lightSrcs[_lightsOrder[i]], _lightDests[_lightsOrder[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
91
engines/nancy/action/puzzle/overridelockpuzzle.h
Normal file
91
engines/nancy/action/puzzle/overridelockpuzzle.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_OVERRIDELOCKPUZZLE_H
|
||||
#define NANCY_ACTION_OVERRIDELOCKPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class OverrideLockPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
static const byte kButtonsStayDown = 1;
|
||||
static const byte kButtonsPopUp = 2;
|
||||
|
||||
static const byte kLightsCircular = 3;
|
||||
static const byte kLightsRandom = 4;
|
||||
|
||||
OverrideLockPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~OverrideLockPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "OverrideLockPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawButton(uint buttonID, bool clear);
|
||||
void drawLights();
|
||||
|
||||
enum SolveState { kNotSolved, kSolved };
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
byte _popButtons = kButtonsStayDown;
|
||||
byte _randomizeLights = kLightsCircular;
|
||||
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<Common::Rect> _lightSrcs;
|
||||
Common::Array<Common::Rect> _lightDests;
|
||||
|
||||
SoundDescription _buttonSound;
|
||||
SoundDescription _wrongSound;
|
||||
|
||||
Time _buttonPopTime;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
Common::Array<byte> _buttonOrder;
|
||||
Common::Array<byte> _lightsOrder;
|
||||
Common::Array<byte> _playerOrder;
|
||||
|
||||
Time _timeToPop;
|
||||
SolveState _solveState = kNotSolved;
|
||||
int8 _lastPushedButton = -1;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_OVERRIDELOCKPUZZLE_H
|
||||
261
engines/nancy/action/puzzle/passwordpuzzle.cpp
Normal file
261
engines/nancy/action/puzzle/passwordpuzzle.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/passwordpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
PasswordPuzzle::~PasswordPuzzle() {
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
}
|
||||
|
||||
void PasswordPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void PasswordPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Serializer s(&stream, nullptr);
|
||||
s.setVersion(g_nancy->getGameType());
|
||||
|
||||
s.syncAsUint16LE(_fontID);
|
||||
s.syncAsUint16LE(_cursorBlinkTime);
|
||||
readRect(s, _nameBounds);
|
||||
readRect(s, _passwordBounds);
|
||||
readRect(s, _screenPosition);
|
||||
|
||||
uint numNames = 1;
|
||||
uint numPasswords = 1;
|
||||
char buf[33];
|
||||
uint fieldSize = s.getVersion() <= kGameTypeNancy5 ? 20 : 33; // nancy6 changed the size of text fields to 33
|
||||
|
||||
s.syncAsUint16LE(numNames, kGameTypeNancy4);
|
||||
_names.resize(numNames);
|
||||
for (uint i = 0; i < numNames; ++i) {
|
||||
stream.read(buf, fieldSize);
|
||||
buf[fieldSize - 1] = '\0';
|
||||
_names[i] = buf;
|
||||
}
|
||||
s.skip((5 - numNames) * fieldSize, kGameTypeNancy4);
|
||||
|
||||
s.syncAsUint16LE(numPasswords, kGameTypeNancy4);
|
||||
_passwords.resize(numPasswords);
|
||||
for (uint i = 0; i < numPasswords; ++i) {
|
||||
stream.read(buf, fieldSize);
|
||||
buf[19] = '\0';
|
||||
_passwords[i] = buf;
|
||||
}
|
||||
s.skip((5 - numPasswords) * fieldSize, kGameTypeNancy4);
|
||||
|
||||
_maxStringLength = g_nancy->getGameType() < kGameTypeNancy6 ? 12 : 31;
|
||||
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
_failExitScene.readData(stream);
|
||||
_failSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void PasswordPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_input->setVKEnabled(true);
|
||||
_nextBlinkTime = g_nancy->getTotalPlayTime() + _cursorBlinkTime;
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved: {
|
||||
Common::String &activeField = _passwordFieldIsActive ? _playerPasswordInput : _playerNameInput;
|
||||
Common::Array<Common::String> &correctAnswers = _passwordFieldIsActive ? _passwords : _names;
|
||||
Time currentTime = g_nancy->getTotalPlayTime();
|
||||
|
||||
if (_playerHasHitReturn) {
|
||||
_playerHasHitReturn = false;
|
||||
|
||||
if (activeField.lastChar() == '-') {
|
||||
activeField.deleteLastChar();
|
||||
drawText();
|
||||
}
|
||||
|
||||
bool solvedCurrentInput = false;
|
||||
if (correctAnswers.size()) {
|
||||
for (uint i = 0; i < correctAnswers.size(); ++i) {
|
||||
if (activeField.equalsIgnoreCase(correctAnswers[i])) {
|
||||
solvedCurrentInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
solvedCurrentInput = true;
|
||||
}
|
||||
|
||||
if (solvedCurrentInput) {
|
||||
if (_passwordFieldIsActive || _passwords.size() == 0) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kSolved;
|
||||
} else {
|
||||
_passwordFieldIsActive = true;
|
||||
}
|
||||
} else {
|
||||
g_nancy->_sound->loadSound(_failSound);
|
||||
g_nancy->_sound->playSound(_failSound);
|
||||
_solveState = kFailed;
|
||||
}
|
||||
} else if (currentTime >= _nextBlinkTime) {
|
||||
_nextBlinkTime = currentTime + _cursorBlinkTime;
|
||||
|
||||
if (activeField.size() && activeField.lastChar() == '-') {
|
||||
activeField.deleteLastChar();
|
||||
} else {
|
||||
activeField += '-';
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kFailed:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_failSound)) {
|
||||
g_nancy->_sound->stopSound(_failSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kSolved:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
_exitScene.execute();
|
||||
break;
|
||||
case kFailed:
|
||||
_failExitScene.execute();
|
||||
break;
|
||||
case kSolved:
|
||||
_solveExitScene.execute();
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordPuzzle::onPause(bool paused) {
|
||||
g_nancy->_input->setVKEnabled(!paused);
|
||||
RenderActionRecord::onPause(paused);
|
||||
}
|
||||
|
||||
void PasswordPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < input.otherKbdInput.size(); ++i) {
|
||||
Common::KeyState &key = input.otherKbdInput[i];
|
||||
Common::String &activeField = _passwordFieldIsActive ? _playerPasswordInput : _playerNameInput;
|
||||
if (key.keycode == Common::KEYCODE_BACKSPACE) {
|
||||
if (activeField.size() && activeField.lastChar() == '-' ? activeField.size() > 1 : true) {
|
||||
if (activeField.lastChar() == '-') {
|
||||
activeField.deleteChar(activeField.size() - 2);
|
||||
} else {
|
||||
activeField.deleteLastChar();
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
} else if (key.keycode == Common::KEYCODE_RETURN || key.keycode == Common::KEYCODE_KP_ENTER) {
|
||||
_playerHasHitReturn = true;
|
||||
} else if (Common::isAlnum(key.ascii) || Common::isSpace(key.ascii)) {
|
||||
if (activeField.size() && activeField.lastChar() == '-') {
|
||||
if (activeField.size() <= _maxStringLength + 1) {
|
||||
activeField.deleteLastChar();
|
||||
activeField += key.ascii;
|
||||
activeField += '-';
|
||||
}
|
||||
} else {
|
||||
if (activeField.size() <= _maxStringLength) {
|
||||
activeField += key.ascii;
|
||||
}
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordPuzzle::drawText() {
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
const Graphics::Font *font = g_nancy->_graphics->getFont(_fontID);
|
||||
|
||||
Common::Rect bounds = _nameBounds;
|
||||
bounds = NancySceneState.getViewport().convertViewportToScreen(bounds);
|
||||
bounds = convertToLocal(bounds);
|
||||
Common::Point destPoint(bounds.left, bounds.bottom + 1 - font->getFontHeight());
|
||||
font->drawString(&_drawSurface, _playerNameInput, destPoint.x, destPoint.y, bounds.width(), 0);
|
||||
|
||||
bounds = _passwordBounds;
|
||||
bounds = NancySceneState.getViewport().convertViewportToScreen(bounds);
|
||||
bounds = convertToLocal(bounds);
|
||||
destPoint.x = bounds.left;
|
||||
destPoint.y = bounds.bottom + 1 - font->getFontHeight();
|
||||
font->drawString(&_drawSurface, _playerPasswordInput, destPoint.x, destPoint.y, bounds.width(), 0);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
76
engines/nancy/action/puzzle/passwordpuzzle.h
Normal file
76
engines/nancy/action/puzzle/passwordpuzzle.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_PASSWORDPUZZLE_H
|
||||
#define NANCY_ACTION_PASSWORDPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class PasswordPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kFailed, kSolved };
|
||||
PasswordPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~PasswordPuzzle();
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void onPause(bool paused) override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
uint16 _fontID = 0;
|
||||
uint16 _cursorBlinkTime = 500;
|
||||
Common::Rect _nameBounds;
|
||||
Common::Rect _passwordBounds;
|
||||
// _screenPosition 0x24
|
||||
Common::Array<Common::String> _names;
|
||||
Common::Array<Common::String> _passwords;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _failExitScene;
|
||||
SoundDescription _failSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Common::String _playerNameInput;
|
||||
Common::String _playerPasswordInput;
|
||||
Time _nextBlinkTime;
|
||||
bool _passwordFieldIsActive = false;
|
||||
bool _playerHasHitReturn = false;
|
||||
SolveState _solveState = kNotSolved;
|
||||
|
||||
uint _maxStringLength = 0;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PasswordPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawText();
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_PASSWORDPUZZLE_H
|
||||
365
engines/nancy/action/puzzle/peepholepuzzle.cpp
Normal file
365
engines/nancy/action/puzzle/peepholepuzzle.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/peepholepuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void PeepholePuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
moveTo(screenBounds);
|
||||
|
||||
Common::Rect innerImageContentBounds;
|
||||
g_nancy->_resource->loadImage(_innerImageName, _innerImage, Common::String(), &innerImageContentBounds);
|
||||
if (!innerImageContentBounds.isEmpty()) {
|
||||
// When using Autotext, make sure scrolling stops with the end of the text content.
|
||||
// This was implemented in nancy7, but it's better to have it on for nancy6 as well.
|
||||
_innerBounds.clip(innerImageContentBounds);
|
||||
}
|
||||
|
||||
if (_buttonsImageName.empty()) {
|
||||
// Empty image name for buttons, use other image as source
|
||||
_buttonsImage.create(_innerImage, _innerImage.getBounds());
|
||||
} else {
|
||||
g_nancy->_resource->loadImage(_buttonsImageName, _buttonsImage);
|
||||
}
|
||||
|
||||
_currentSrc = _startSrc;
|
||||
|
||||
setTransparent(_transparency == kPlayOverlayTransparent);
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
setVisible(true);
|
||||
|
||||
drawInner();
|
||||
checkButtons();
|
||||
}
|
||||
|
||||
void PeepholePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _innerImageName);
|
||||
readFilename(stream, _buttonsImageName);
|
||||
|
||||
_transparency = stream.readUint16LE();
|
||||
|
||||
readRect(stream, _innerBounds);
|
||||
readRect(stream, _startSrc);
|
||||
readRect(stream, _dest);
|
||||
|
||||
readRectArray(stream, _buttonDests, 4);
|
||||
readRectArray(stream, _buttonSrcs, 4);
|
||||
readRectArray(stream, _buttonDisabledSrcs, 4);
|
||||
|
||||
_pixelsToScroll = stream.readByte();
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void PeepholePuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
break;
|
||||
case kActionTrigger:
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void PeepholePuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool justReleased = false;
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pressedButton != -1) {
|
||||
if (input.input & NancyInput::kLeftMouseButtonHeld) {
|
||||
// Player is still holding the left button, check if mouse has moved outside bounds
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[_pressedButton]).contains(input.mousePos)) {
|
||||
// NOTE: Contrary to the original, we don't change the cursor to a hotspot over disabled buttons
|
||||
if (!_disabledButtons[_pressedButton]) {
|
||||
// Do not show hover cursor on disabled button
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
}
|
||||
|
||||
if (_pressStart == 0) {
|
||||
// Mouse was out of bounds but still held, and is now back in bounds, continue moving
|
||||
_pressStart = g_nancy->getTotalPlayTime();
|
||||
}
|
||||
} else {
|
||||
// Mouse is not in bounds, pause moving
|
||||
_pressStart = 0;
|
||||
justReleased = true;
|
||||
}
|
||||
} else {
|
||||
// Player released mouse button
|
||||
|
||||
// Avoid single frame with non-highlighted cursor
|
||||
// NOTE: Contrary to the original, we don't change the cursor to a hotspot over disabled buttons
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[_pressedButton]).contains(input.mousePos) && !_disabledButtons[_pressedButton]) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
}
|
||||
|
||||
_pressedButton = -1;
|
||||
_pressStart = 0;
|
||||
justReleased = true;
|
||||
}
|
||||
} else {
|
||||
// Mouse is not currently pressing button, check all buttons
|
||||
for (uint i = 0; i < 4; ++i) {
|
||||
// NOTE: Contrary to the original, we don't change the cursor to a hotspot over disabled buttons
|
||||
if (!_disabledButtons[i]) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonDown) {
|
||||
if (_pressedButton == -1) {
|
||||
// Just pressed
|
||||
_pressedButton = i;
|
||||
_pressStart = g_nancy->getTotalPlayTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform movement
|
||||
if (_pressedButton != -1 && _pressStart != 0) {
|
||||
uint32 curTime = g_nancy->getTotalPlayTime();
|
||||
int16 pixelsToMove = 0;
|
||||
if (curTime - _pressStart >= 1000u / _pixelsToScroll) {
|
||||
pixelsToMove = (curTime - _pressStart) / (1000 / _pixelsToScroll);
|
||||
}
|
||||
|
||||
switch (_pressedButton) {
|
||||
case 0 :
|
||||
// Up
|
||||
_currentSrc.translate(0, -pixelsToMove);
|
||||
if (_currentSrc.top < _innerBounds.top) {
|
||||
_currentSrc.translate(0, _innerBounds.top - _currentSrc.top);
|
||||
}
|
||||
break;
|
||||
case 1 :
|
||||
// Down
|
||||
_currentSrc.translate(0, pixelsToMove);
|
||||
if (_currentSrc.bottom > _innerBounds.bottom) {
|
||||
_currentSrc.translate(0, _innerBounds.bottom - _currentSrc.bottom);
|
||||
}
|
||||
break;
|
||||
case 2 :
|
||||
// Left
|
||||
_currentSrc.translate(-pixelsToMove, 0);
|
||||
if (_currentSrc.left < _innerBounds.left) {
|
||||
_currentSrc.translate(_innerBounds.left - _currentSrc.left, 0);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// Right
|
||||
_currentSrc.translate(pixelsToMove, 0);
|
||||
if (_currentSrc.right > _innerBounds.right) {
|
||||
_currentSrc.translate(_innerBounds.right - _currentSrc.right, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_pressStart = curTime;
|
||||
|
||||
checkButtons();
|
||||
drawInner();
|
||||
} else if (justReleased) {
|
||||
checkButtons();
|
||||
}
|
||||
}
|
||||
|
||||
void PeepholePuzzle::drawInner() {
|
||||
_drawSurface.blitFrom(_innerImage, _currentSrc, _dest);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void PeepholePuzzle::checkButtons() {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int16 *srcCoord = nullptr;
|
||||
int16 *innerCoord = nullptr;
|
||||
|
||||
switch(i) {
|
||||
case 0 :
|
||||
srcCoord = &_currentSrc.top;
|
||||
innerCoord = &_innerBounds.top;
|
||||
break;
|
||||
case 1 :
|
||||
srcCoord = &_currentSrc.bottom;
|
||||
innerCoord = &_innerBounds.bottom;
|
||||
break;
|
||||
case 2 :
|
||||
srcCoord = &_currentSrc.left;
|
||||
innerCoord = &_innerBounds.left;
|
||||
break;
|
||||
case 3 :
|
||||
srcCoord = &_currentSrc.right;
|
||||
innerCoord = &_innerBounds.right;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_buttonDests[i].isEmpty()) {
|
||||
if (*srcCoord == *innerCoord) {
|
||||
if (_disabledButtons[i] == false) {
|
||||
_disabledButtons[i] = true;
|
||||
if (!_buttonDisabledSrcs[i].isEmpty()) {
|
||||
_drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[i], _buttonDests[i]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_disabledButtons[i] = false;
|
||||
if (i == _pressedButton && _pressStart) {
|
||||
_drawSurface.blitFrom(_buttonsImage, _buttonSrcs[i], _buttonDests[i]);
|
||||
_needsRedraw = true;
|
||||
} else {
|
||||
_drawSurface.fillRect(_buttonDests[i], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_disabledButtons[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that contents that do not overflow can't be scrolled
|
||||
if (_innerBounds.height() <= _dest.height()) {
|
||||
_disabledButtons[0] = _disabledButtons[1] = true;
|
||||
|
||||
if (!_buttonDisabledSrcs[0].isEmpty()) {
|
||||
_drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[0], _buttonDests[0]);
|
||||
}
|
||||
|
||||
if (!_buttonDisabledSrcs[1].isEmpty()) {
|
||||
_drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[1], _buttonDests[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (_innerBounds.width() <= _dest.width()) {
|
||||
_disabledButtons[2] = _disabledButtons[3] = true;
|
||||
|
||||
if (!_buttonDisabledSrcs[2].isEmpty()) {
|
||||
_drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[2], _buttonDests[2]);
|
||||
}
|
||||
|
||||
if (!_buttonDisabledSrcs[3].isEmpty()) {
|
||||
_drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[3], _buttonDests[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextScroll::init() {
|
||||
Autotext::execute();
|
||||
_isDone = false; // Set to true by Autotext
|
||||
|
||||
// Supply the correct names to the resource manager
|
||||
if (_surfaceID < 3) {
|
||||
_innerImageName = Common::String::format("USE_AUTOTEXT%u", _surfaceID + 1);
|
||||
} else {
|
||||
_innerImageName = Common::String::format("USE_AUTOLIST%u", _surfaceID - 2);
|
||||
}
|
||||
|
||||
// Make sure the initial bounds match the surface's
|
||||
_innerBounds = _fullSurface.getBounds();
|
||||
|
||||
PeepholePuzzle::init();
|
||||
}
|
||||
|
||||
void TextScroll::readData(Common::SeekableReadStream &stream) {
|
||||
Autotext::readData(stream);
|
||||
|
||||
PeepholePuzzle::_transparency = Autotext::_transparency;
|
||||
}
|
||||
|
||||
void TextScroll::readExtraData(Common::SeekableReadStream &stream) {
|
||||
_order = stream.readUint16LE();
|
||||
_shouldDrawMarks = stream.readByte();
|
||||
|
||||
readFilename(stream, _buttonsImageName);
|
||||
|
||||
readRect(stream, _startSrc);
|
||||
readRect(stream, _dest);
|
||||
|
||||
readRectArray(stream, _buttonDests, 4);
|
||||
readRectArray(stream, _buttonSrcs, 4);
|
||||
readRectArray(stream, _buttonDisabledSrcs, 4);
|
||||
|
||||
_pixelsToScroll = stream.readByte();
|
||||
|
||||
if (!_isEntryList) {
|
||||
Autotext::readExtraData(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void TextScroll::handleInput(NancyInput &input) {
|
||||
PeepholePuzzle::handleInput(input);
|
||||
|
||||
// Finally, check hotspots
|
||||
for (uint i = 0; i < _hotspots.size(); ++i) {
|
||||
Common::Rect hotspot = _hotspots[i];
|
||||
hotspot.translate(_dest.left, _dest.top);
|
||||
Common::Point innerOffset = _drawSurface.getOffsetFromOwner();
|
||||
hotspot.translate(-innerOffset.x, -innerOffset.y);
|
||||
hotspot.clip(_dest);
|
||||
if (!hotspot.isEmpty() && NancySceneState.getViewport().convertViewportToScreen(hotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Clicked on a hotspot, change to corresponding scene
|
||||
SceneChangeDescription sceneChange;
|
||||
sceneChange.sceneID = _hotspotScenes[i];
|
||||
sceneChange.continueSceneSound = kContinueSceneSound;
|
||||
NancySceneState.changeScene(sceneChange);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
102
engines/nancy/action/puzzle/peepholepuzzle.h
Normal file
102
engines/nancy/action/puzzle/peepholepuzzle.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_PEEPHOLEPUZZLE_H
|
||||
#define NANCY_ACTION_PEEPHOLEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/autotext.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Action record that, despite what its name suggests, is mostly used
|
||||
// to render Nancy's diary in nancy6 and up.
|
||||
class PeepholePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
PeepholePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~PeepholePuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PeepholePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawInner();
|
||||
void checkButtons();
|
||||
|
||||
Common::Path _innerImageName;
|
||||
Common::Path _buttonsImageName;
|
||||
|
||||
uint16 _transparency = 0;
|
||||
|
||||
Common::Rect _innerBounds;
|
||||
Common::Rect _startSrc;
|
||||
Common::Rect _dest;
|
||||
|
||||
// Order: up, down, left, right
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
Common::Array<Common::Rect> _buttonDisabledSrcs;
|
||||
|
||||
byte _pixelsToScroll = 0;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _innerImage;
|
||||
Graphics::ManagedSurface _buttonsImage;
|
||||
|
||||
Common::Rect _currentSrc;
|
||||
int _pressedButton = -1;
|
||||
uint32 _pressStart = 0;
|
||||
Common::Array<bool> _disabledButtons = Common::Array<bool>(4, false);
|
||||
};
|
||||
|
||||
// Combines the Peephole with the Autotext used to supply its text data
|
||||
// Implementing this with diamond-shaped multiple inheritance is bad,
|
||||
// but so is the original design, where a Peephole is constructed
|
||||
// on the fly and replaces the TextScroll/AutotextEntryList
|
||||
class TextScroll : public Autotext, public PeepholePuzzle {
|
||||
public:
|
||||
TextScroll(bool isEntryList) : _isEntryList(isEntryList) {}
|
||||
|
||||
void init() override;
|
||||
void execute() override { PeepholePuzzle::execute(); }
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return _isEntryList ? "AutotextEntryList" : "TextScroll"; }
|
||||
void readExtraData(Common::SeekableReadStream &stream) override;
|
||||
|
||||
bool _isEntryList;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_PEEPHOLEPUZZLE_H
|
||||
96
engines/nancy/action/puzzle/quizpuzzle.cpp
Normal file
96
engines/nancy/action/puzzle/quizpuzzle.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/quizpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void QuizPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void QuizPuzzle::execute() {
|
||||
// TODO
|
||||
|
||||
if (g_nancy->getGameType() == kGameTypeNancy8) {
|
||||
warning("STUB - Nancy 8 Quiz Puzzle");
|
||||
} else if (g_nancy->getGameType() == kGameTypeNancy9) {
|
||||
const uint16 sceneId = NancySceneState.getSceneInfo().sceneID;
|
||||
if (sceneId == 6450) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Holt Scotto's quiz, page 1");
|
||||
// Set the puzzle event flags to flag it as done
|
||||
NancySceneState.setEventFlag(59, g_nancy->_true); // EV_Answered_SQ_Q06
|
||||
} else if (sceneId == 6451) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Holt Scotto's quiz, page 2");
|
||||
// Set the puzzle event flags to flag it as done
|
||||
NancySceneState.setEventFlag(61, g_nancy->_true); // EV_Answered_SQ_Q09
|
||||
} else if (sceneId == 6342) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - GPS new waypoint");
|
||||
// Set the GPS waypoint as discovered
|
||||
NancySceneState.setEventFlag(410, g_nancy->_true); // EV_Solved_GPS_Beach
|
||||
} else if (sceneId == 6344) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - GPS new waypoint - cache A");
|
||||
// Set the GPS waypoint as discovered
|
||||
NancySceneState.setEventFlag(411, g_nancy->_true); // EV_Solved_GPS_CacheA
|
||||
} else if (sceneId == 6345) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - GPS new waypoint - cache B");
|
||||
// Set the GPS waypoint as discovered
|
||||
NancySceneState.setEventFlag(412, g_nancy->_true); // EV_Solved_GPS_CacheB
|
||||
} else if (sceneId == 6431) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Hilda Swenson's letter");
|
||||
NancySceneState.setEventFlag(179, g_nancy->_true); // EV_Hilda_Said_Objects
|
||||
} else if (sceneId == 6443) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Holt Scotto's chess problem");
|
||||
NancySceneState.setEventFlag(119, g_nancy->_true); // EV_Finished_Chess_Quiz
|
||||
} else if (sceneId == 4184) {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle - Lighthouse Morse code");
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 4190;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
} else {
|
||||
warning("STUB - Nancy 9 Quiz Puzzle");
|
||||
}
|
||||
}
|
||||
|
||||
_isDone = true;
|
||||
}
|
||||
|
||||
void QuizPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void QuizPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
50
engines/nancy/action/puzzle/quizpuzzle.h
Normal file
50
engines/nancy/action/puzzle/quizpuzzle.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_QUIZPUZZLE_H
|
||||
#define NANCY_ACTION_QUIZPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Stenography tutorial in Nancy 8
|
||||
|
||||
class QuizPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
QuizPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~QuizPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "QuizPuzzle"; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_QUIZPUZZLE_H
|
||||
2089
engines/nancy/action/puzzle/raycastpuzzle.cpp
Normal file
2089
engines/nancy/action/puzzle/raycastpuzzle.cpp
Normal file
File diff suppressed because it is too large
Load Diff
139
engines/nancy/action/puzzle/raycastpuzzle.h
Normal file
139
engines/nancy/action/puzzle/raycastpuzzle.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_RAYCASTPUZZLE_H
|
||||
#define NANCY_ACTION_RAYCASTPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct RCPR;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class RaycastDeferredLoader;
|
||||
class RaycastLevelBuilder;
|
||||
|
||||
// Action record implementing nancy3's maze minigame
|
||||
class RaycastPuzzle : public RenderActionRecord {
|
||||
friend class RaycastDeferredLoader;
|
||||
friend class RaycastLevelBuilder;
|
||||
public:
|
||||
RaycastPuzzle() : RenderActionRecord(7), _map(7) {}
|
||||
~RaycastPuzzle() override;
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void onPause(bool pause) override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
void updateGraphics() override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "RaycastPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void validateMap();
|
||||
|
||||
void createTextureLightSourcing(Common::Array<Graphics::ManagedSurface> *array, const Common::Path &textureName);
|
||||
|
||||
void drawMap();
|
||||
void updateMap();
|
||||
void drawMaze();
|
||||
void clearZBuffer();
|
||||
|
||||
void checkSwitch();
|
||||
void checkExit();
|
||||
|
||||
uint16 _mapWidth = 0;
|
||||
uint16 _mapHeight = 0;
|
||||
byte _wallHeight = 0;
|
||||
|
||||
Common::String _switchSoundName;
|
||||
uint16 _switchSoundChannelID = 0;
|
||||
Common::String _unknownSoundName;
|
||||
uint16 _unknownSoundChannelID = 0;
|
||||
SoundDescription _dummySound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
Common::Array<uint32> _wallMap, _infoMap;
|
||||
Common::Array<int16> _floorMap, _ceilingMap;
|
||||
Common::Array<uint16> _wallLightMap, _floorCeilingLightMap, _heightMap;
|
||||
Common::Array<uint16> _wallLightMapBackup, _floorCeilingLightMapBackup;
|
||||
|
||||
uint16 _mapFullWidth = 0;
|
||||
uint16 _mapFullHeight = 0;
|
||||
|
||||
RenderObject _map;
|
||||
Graphics::ManagedSurface _mapBaseSurface;
|
||||
|
||||
double _pi = 3.141592653589793;
|
||||
uint _fov = 192;
|
||||
Common::Array<float> _sinTable, _cosTable;
|
||||
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _wallTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _specialWallTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _ceilingTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _floorTextures;
|
||||
Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _exitFloorTextures;
|
||||
|
||||
Common::Array<int32> _wallCastColumnAngles;
|
||||
Common::Array<byte> _zBuffer;
|
||||
byte _lastZDepth = 0;
|
||||
Common::Array<float> _depthBuffer;
|
||||
int32 _leftmostAngle = -1;
|
||||
int32 _rightmostAngle = -1;
|
||||
|
||||
// Improvement: we store position as float for smoother movement
|
||||
float _playerX = -1; // Player position with precision 1/128th of cell width/height
|
||||
float _playerY = -1;
|
||||
int32 _playerRotation = 0; // Rotation of player (0 - 4096)
|
||||
uint32 _playerAltitude = 88; // Z position of "camera"; only modified in god mode
|
||||
|
||||
float _rotationSingleStep = 4096.0 / (_pi * 2);
|
||||
float _maxWorldDistance = 0.0;
|
||||
|
||||
uint32 _lastMovementTime = 0;
|
||||
int _lastMouseX = -1;
|
||||
|
||||
uint32 _nextSlowdownMovementTime = 0;
|
||||
byte _slowdownFramesLeft = 0;
|
||||
int32 _slowdownDeltaX = -1;
|
||||
int32 _slowdownDeltaY = -1;
|
||||
|
||||
Common::Array<byte> _lightSwitchIDs;
|
||||
Common::Array<Common::Point> _lightSwitchPositions;
|
||||
Common::Array<bool> _lightSwitchStates;
|
||||
int _lightSwitchPlayerIsOn = -1;
|
||||
|
||||
const RCPR *_puzzleData = nullptr;
|
||||
Common::SharedPtr<RaycastDeferredLoader> _loaderPtr;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_RAYCASTPUZZLE_H
|
||||
343
engines/nancy/action/puzzle/riddlepuzzle.cpp
Normal file
343
engines/nancy/action/puzzle/riddlepuzzle.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
/* 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/sound.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/riddlepuzzle.h"
|
||||
|
||||
#include "common/random.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
RiddlePuzzle::~RiddlePuzzle() {
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
}
|
||||
|
||||
void RiddlePuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void RiddlePuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (RiddlePuzzleData *)NancySceneState.getPuzzleData(RiddlePuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
_viewportTextFontID = stream.readUint16LE();
|
||||
_textboxTextFontID = stream.readUint16LE();
|
||||
_cursorBlinkTime = stream.readUint16LE();
|
||||
readRect(stream, _screenPosition);
|
||||
_typeSound.readNormal(stream);
|
||||
_eraseSound.readNormal(stream);
|
||||
_enterSound.readNormal(stream);
|
||||
_successSceneChange.readData(stream);
|
||||
_successSound.readNormal(stream);
|
||||
_exitSceneChange.readData(stream);
|
||||
_exitSound.readNormal(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
|
||||
_riddles.resize(stream.readUint16LE()) ;
|
||||
stream.skip(4);
|
||||
|
||||
char buf[128];
|
||||
for (uint i = 0; i < _riddles.size(); ++i) {
|
||||
Riddle &riddle = _riddles[i];
|
||||
|
||||
stream.read(buf, 128);
|
||||
buf[127] = '\0';
|
||||
riddle.text = buf;
|
||||
riddle.sound.readNormal(stream);
|
||||
|
||||
for (uint j = 0; j < 8; ++j) {
|
||||
stream.read(buf, 20);
|
||||
buf[19] = '\0';
|
||||
Common::String answer = buf;
|
||||
if (!answer.empty()) {
|
||||
riddle.answers.push_back(answer);
|
||||
}
|
||||
}
|
||||
|
||||
riddle.sceneIncorrect.readData(stream);
|
||||
riddle.soundIncorrect.readNormal(stream);
|
||||
riddle.sceneCorrect.readData(stream);
|
||||
riddle.soundCorrect.readNormal(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void RiddlePuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
_puzzleState = (RiddlePuzzleData *)NancySceneState.getPuzzleData(RiddlePuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
_nextBlinkTime = g_nancy->getTotalPlayTime() + _cursorBlinkTime;
|
||||
|
||||
g_nancy->_sound->loadSound(_typeSound);
|
||||
g_nancy->_sound->loadSound(_eraseSound);
|
||||
g_nancy->_sound->loadSound(_enterSound);
|
||||
|
||||
// Make a list of non-answered riddle IDs
|
||||
Common::Array<byte> availableIDs;
|
||||
for (uint i = 0; i < _riddles.size(); ++i) {
|
||||
bool isAlreadySolved = false;
|
||||
for (auto id : _puzzleState->solvedRiddleIDs) {
|
||||
if (i == id) {
|
||||
isAlreadySolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAlreadySolved) {
|
||||
availableIDs.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (availableIDs.size() == 0) {
|
||||
_solveState = kSolvedAll;
|
||||
_state = kRun;
|
||||
break;
|
||||
} else {
|
||||
if (_puzzleState->incorrectRiddleID != -1) {
|
||||
_riddleID = _puzzleState->incorrectRiddleID;
|
||||
} else {
|
||||
_riddleID = availableIDs[g_nancy->_randomSource->getRandomNumber(availableIDs.size() - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_riddles[_riddleID].sound);
|
||||
g_nancy->_sound->playSound(_riddles[_riddleID].sound);
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().setOverrideFont(_textboxTextFontID);
|
||||
NancySceneState.getTextbox().addTextLine(_riddles[_riddleID].text);
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
}
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_riddles[_riddleID].sound)) {
|
||||
_solveState = kNotSolved;
|
||||
g_nancy->_input->setVKEnabled(true);
|
||||
}
|
||||
|
||||
break;
|
||||
case kNotSolved: {
|
||||
Time currentTime = g_nancy->getTotalPlayTime();
|
||||
|
||||
if (_playerHasHitReturn) {
|
||||
_playerHasHitReturn = false;
|
||||
|
||||
if (_playerInput.lastChar() == '-') {
|
||||
_playerInput.deleteLastChar();
|
||||
drawText();
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(_enterSound)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (Common::String &answer : _riddles[_riddleID].answers) {
|
||||
if (_playerInput.equalsIgnoreCase(answer)) {
|
||||
// Solved a riddle
|
||||
_puzzleState->solvedRiddleIDs.push_back(_riddleID);
|
||||
|
||||
if (_puzzleState->solvedRiddleIDs.size() == _riddles.size()) {
|
||||
// Solved all riddles
|
||||
g_nancy->_sound->loadSound(_successSound);
|
||||
g_nancy->_sound->playSound(_successSound);
|
||||
_solveState = kSolvedAll;
|
||||
_state = kActionTrigger;
|
||||
|
||||
break;
|
||||
} else {
|
||||
// Still have riddles to solve
|
||||
g_nancy->_sound->loadSound(_riddles[_riddleID].soundCorrect);
|
||||
g_nancy->_sound->playSound(_riddles[_riddleID].soundCorrect);
|
||||
_solveState = kSolvedOne;
|
||||
_state = kActionTrigger;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
// Did not solve a riddle
|
||||
g_nancy->_sound->loadSound(_riddles[_riddleID].soundIncorrect);
|
||||
g_nancy->_sound->playSound(_riddles[_riddleID].soundIncorrect);
|
||||
_solveState = kFailed;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
} else if (currentTime >= _nextBlinkTime) {
|
||||
_nextBlinkTime = currentTime + _cursorBlinkTime;
|
||||
|
||||
if (_playerInput.size() && _playerInput.lastChar() == '-') {
|
||||
_playerInput.deleteLastChar();
|
||||
} else {
|
||||
_playerInput += '-';
|
||||
}
|
||||
|
||||
drawText();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger: {
|
||||
SoundDescription *sound = nullptr;
|
||||
SceneChangeWithFlag *sceneChange = nullptr;
|
||||
_puzzleState->incorrectRiddleID = -1;
|
||||
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
sound = &_exitSound;
|
||||
sceneChange = &_exitSceneChange;
|
||||
|
||||
break;
|
||||
case kFailed:
|
||||
sound = &_riddles[_riddleID].soundIncorrect;
|
||||
sceneChange = &_riddles[_riddleID].sceneIncorrect;
|
||||
_puzzleState->incorrectRiddleID = _riddleID;
|
||||
|
||||
break;
|
||||
case kSolvedOne:
|
||||
sound = &_riddles[_riddleID].soundCorrect;
|
||||
sceneChange = &_riddles[_riddleID].sceneCorrect;
|
||||
|
||||
break;
|
||||
case kSolvedAll:
|
||||
sound = &_successSound;
|
||||
sceneChange = &_successSceneChange;
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(*sound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(*sound);
|
||||
g_nancy->_sound->stopSound(_typeSound);
|
||||
g_nancy->_sound->stopSound(_eraseSound);
|
||||
g_nancy->_sound->stopSound(_enterSound);
|
||||
|
||||
sceneChange->execute();
|
||||
g_nancy->_input->setVKEnabled(false);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RiddlePuzzle::onPause(bool paused) {
|
||||
g_nancy->_input->setVKEnabled(!paused);
|
||||
RenderActionRecord::onPause(paused);
|
||||
}
|
||||
|
||||
void RiddlePuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < input.otherKbdInput.size(); ++i) {
|
||||
Common::KeyState &key = input.otherKbdInput[i];
|
||||
if (key.keycode == Common::KEYCODE_BACKSPACE) {
|
||||
if (_playerInput.size() && _playerInput.lastChar() == '-' ? _playerInput.size() > 1 : true) {
|
||||
if (_playerInput.lastChar() == '-') {
|
||||
_playerInput.deleteChar(_playerInput.size() - 2);
|
||||
} else {
|
||||
_playerInput.deleteLastChar();
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_eraseSound);
|
||||
|
||||
drawText();
|
||||
}
|
||||
} else if (key.keycode == Common::KEYCODE_RETURN || key.keycode == Common::KEYCODE_KP_ENTER) {
|
||||
if (_playerInput.size() == 0 ||
|
||||
(_playerInput.size() == 1 && _playerInput.lastChar() == '-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_playerHasHitReturn = true;
|
||||
g_nancy->_sound->playSound(_enterSound);
|
||||
} else if (Common::isAlnum(key.ascii) || Common::isSpace(key.ascii)) {
|
||||
if (_playerInput.size() && _playerInput.lastChar() == '-') {
|
||||
if (_playerInput.size() <= 16) {
|
||||
_playerInput.deleteLastChar();
|
||||
_playerInput += key.ascii;
|
||||
_playerInput += '-';
|
||||
g_nancy->_sound->playSound(_typeSound);
|
||||
drawText();
|
||||
}
|
||||
} else {
|
||||
if (_playerInput.size() <= 15) {
|
||||
_playerInput += key.ascii;
|
||||
g_nancy->_sound->playSound(_typeSound);
|
||||
drawText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RiddlePuzzle::drawText() {
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
const Graphics::Font *font = g_nancy->_graphics->getFont(_viewportTextFontID);
|
||||
|
||||
Common::Rect bounds = getBounds();
|
||||
Common::Point destPoint(bounds.left, bounds.bottom - font->getFontHeight());
|
||||
font->drawString(&_drawSurface, _playerInput, destPoint.x, destPoint.y, bounds.width(), 0);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
86
engines/nancy/action/puzzle/riddlepuzzle.h
Normal file
86
engines/nancy/action/puzzle/riddlepuzzle.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_RIDDLEPUZZLE_H
|
||||
#define NANCY_ACTION_RIDDLEPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct RiddlePuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class RiddlePuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kWaitForSound, kNotSolved, kFailed, kSolvedOne, kSolvedAll };
|
||||
RiddlePuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~RiddlePuzzle();
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void onPause(bool paused) override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
struct Riddle {
|
||||
Common::String text;
|
||||
SoundDescription sound;
|
||||
Common::Array<Common::String> answers;
|
||||
SceneChangeWithFlag sceneIncorrect;
|
||||
SoundDescription soundIncorrect;
|
||||
SceneChangeWithFlag sceneCorrect;
|
||||
SoundDescription soundCorrect;
|
||||
};
|
||||
|
||||
Common::String getRecordTypeName() const override { return "RiddlePuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawText();
|
||||
|
||||
uint16 _viewportTextFontID = 0;
|
||||
uint16 _textboxTextFontID = 0;
|
||||
Time _cursorBlinkTime;
|
||||
SoundDescription _typeSound;
|
||||
SoundDescription _eraseSound;
|
||||
SoundDescription _enterSound;
|
||||
SceneChangeWithFlag _successSceneChange;
|
||||
SoundDescription _successSound;
|
||||
SceneChangeWithFlag _exitSceneChange;
|
||||
SoundDescription _exitSound;
|
||||
Common::Rect _exitHotspot;
|
||||
Common::Array<Riddle> _riddles;
|
||||
|
||||
Time _nextBlinkTime;
|
||||
SolveState _solveState = kWaitForSound;
|
||||
bool _playerHasHitReturn = false;
|
||||
Common::String _playerInput;
|
||||
uint _riddleID = 0;
|
||||
RiddlePuzzleData *_puzzleState = nullptr;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_RIDDLEPUZZLE_H
|
||||
466
engines/nancy/action/puzzle/rippedletterpuzzle.cpp
Normal file
466
engines/nancy/action/puzzle/rippedletterpuzzle.cpp
Normal file
@@ -0,0 +1,466 @@
|
||||
/* 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/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/cursor.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/rippedletterpuzzle.h"
|
||||
|
||||
#include "graphics/transform_struct.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void RippedLetterPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
|
||||
if (_useCustomPickUpTile) {
|
||||
_pickedUpPiece._drawSurface.create(_image, _customPickUpTileSrc);
|
||||
} else {
|
||||
_pickedUpPiece._drawSurface.create(_destRects[0].width(), _destRects[0].height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
}
|
||||
|
||||
_pickedUpPiece.setVisible(false);
|
||||
}
|
||||
|
||||
void RippedLetterPuzzle::registerGraphics() {
|
||||
_pickedUpPiece.registerGraphics();
|
||||
RenderObject::registerGraphics();
|
||||
}
|
||||
|
||||
void RippedLetterPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (RippedLetterPuzzleData *)NancySceneState.getPuzzleData(RippedLetterPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
byte maxWidth = 6;
|
||||
byte maxHeight = g_nancy->getGameType() <= kGameTypeNancy6 ? 4 : 5;
|
||||
byte width = maxWidth;
|
||||
byte height = maxHeight;
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy5) {
|
||||
width = stream.readByte();
|
||||
height = stream.readByte();
|
||||
}
|
||||
|
||||
// All the checks for whether width is greater than maxWidth are
|
||||
// to account for nancy9 scene 2428, where the dimensions are 15x1
|
||||
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
readRectArray(stream, _srcRects, width, maxWidth);
|
||||
}
|
||||
stream.skip((maxWidth >= width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * 16);
|
||||
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
readRectArray(stream, _destRects, width, maxWidth);
|
||||
}
|
||||
stream.skip((maxWidth >= width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * 16);
|
||||
|
||||
readRect(stream, _rotateHotspot);
|
||||
readRect(stream, _takeHotspot);
|
||||
readRect(stream, _dropHotspot);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy7) {
|
||||
_rotationType = (RotationType)stream.readUint16LE();
|
||||
}
|
||||
|
||||
uint elemSize = g_nancy->getGameType() <= kGameTypeNancy8 ? 1 : 2;
|
||||
|
||||
_initOrder.resize(width * height);
|
||||
assert(width * height <= 24); // If this gets hit we need to increase the sizes in RippedLetterPuzzleData
|
||||
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
for (uint j = 0; j < width; ++j) {
|
||||
_initOrder[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
||||
}
|
||||
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
||||
}
|
||||
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
||||
|
||||
_initRotations.resize(width * height);
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
for (uint j = 0; j < width; ++j) {
|
||||
_initRotations[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
||||
}
|
||||
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
||||
}
|
||||
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
||||
uint16 numDoubledElements = stream.readUint16LE();
|
||||
_doubles.resize(numDoubledElements);
|
||||
uint i = 0;
|
||||
for (uint j = 0; j < 20; ++j) {
|
||||
int16 id = stream.readSint16LE();
|
||||
if (id == -1) {
|
||||
++i;
|
||||
} else {
|
||||
_doubles[i].push_back(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_solveOrder.resize(width * height);
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
for (uint j = 0; j < width; ++j) {
|
||||
_solveOrder[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
||||
}
|
||||
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
||||
}
|
||||
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
||||
|
||||
_solveRotations.resize(width * height);
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
for (uint j = 0; j < width; ++j) {
|
||||
_solveRotations[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
||||
}
|
||||
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
||||
}
|
||||
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
||||
_useAltSolution = stream.readByte();
|
||||
|
||||
_solveOrderAlt.resize(width * height);
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
for (uint j = 0; j < width; ++j) {
|
||||
_solveOrderAlt[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
||||
}
|
||||
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
||||
}
|
||||
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
||||
|
||||
_solveRotationsAlt.resize(width * height);
|
||||
for (uint i = 0; i < height; ++i) {
|
||||
for (uint j = 0; j < width; ++j) {
|
||||
_solveRotationsAlt[i * width + j] = (elemSize == 1 ? stream.readByte() : stream.readSint16LE());
|
||||
}
|
||||
stream.skip(maxWidth > width ? (maxWidth - width) * elemSize : 0);
|
||||
}
|
||||
stream.skip((maxWidth > width ? (maxHeight - height) * maxWidth : maxWidth * maxHeight - width * height) * elemSize);
|
||||
}
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy7) {
|
||||
_useCustomPickUpTile = stream.readByte();
|
||||
readRect(stream, _customPickUpTileSrc);
|
||||
}
|
||||
|
||||
_takeSound.readNormal(stream);
|
||||
_dropSound.readNormal(stream);
|
||||
_rotateSound.readNormal(stream);
|
||||
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
||||
_customCursorID = stream.readSint16LE();
|
||||
}
|
||||
}
|
||||
|
||||
void RippedLetterPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (RippedLetterPuzzleData *)NancySceneState.getPuzzleData(RippedLetterPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
if (!_puzzleState->playerHasTriedPuzzle) {
|
||||
_puzzleState->order = _initOrder;
|
||||
_puzzleState->rotations = _initRotations;
|
||||
_puzzleState->playerHasTriedPuzzle = true;
|
||||
} else if (_puzzleState->_pickedUpPieceID != -1) {
|
||||
// Puzzle was left while still holding a piece (e.g. by clicking a scene item).
|
||||
// Make sure we put the held piece back in its place
|
||||
_puzzleState->order[_puzzleState->_pickedUpPieceLastPos] = _puzzleState->_pickedUpPieceID;
|
||||
_puzzleState->rotations[_puzzleState->_pickedUpPieceLastPos] = _puzzleState->_pickedUpPieceRot;
|
||||
_puzzleState->_pickedUpPieceID = -1;
|
||||
_puzzleState->_pickedUpPieceLastPos = -1;
|
||||
_puzzleState->_pickedUpPieceRot = 0;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _puzzleState->order.size(); ++i) {
|
||||
drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_takeSound);
|
||||
g_nancy->_sound->loadSound(_dropSound);
|
||||
g_nancy->_sound->loadSound(_rotateSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved :
|
||||
for (uint i = 0; i < _puzzleState->order.size(); ++i) {
|
||||
if (_puzzleState->rotations[i] != _solveRotations[i] || !checkOrder(false)) {
|
||||
if (_useAltSolution) {
|
||||
if (!checkOrder(true)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound :
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
_exitScene.execute();
|
||||
break;
|
||||
case kWaitForSound:
|
||||
if (_solveExitScene._sceneChange.sceneID == NancySceneState.getSceneInfo().sceneID) {
|
||||
// nancy9 scene 2484 is auto-solved for you, but has a valid scene change back to itself
|
||||
return;
|
||||
}
|
||||
_solveExitScene.execute();
|
||||
_puzzleState->playerHasTriedPuzzle = false;
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_takeSound);
|
||||
g_nancy->_sound->stopSound(_dropSound);
|
||||
g_nancy->_sound->stopSound(_rotateSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void RippedLetterPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state == kBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _puzzleState->order.size(); ++i) {
|
||||
Common::Rect screenHotspot = NancySceneState.getViewport().convertViewportToScreen(_destRects[i]);
|
||||
if (screenHotspot.contains(input.mousePos)) {
|
||||
Common::Rect insideRect;
|
||||
if (_puzzleState->_pickedUpPieceID == -1) {
|
||||
// No piece picked up
|
||||
|
||||
// Check if the mouse is inside the rotation hotspot
|
||||
insideRect = _rotateHotspot;
|
||||
insideRect.translate(screenHotspot.left, screenHotspot.top);
|
||||
|
||||
if (_rotationType != kRotationNone && insideRect.contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kRotateCW);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Player has clicked, rotate the piece
|
||||
int inc = (_rotationType == kRotation90 ? 1 : 2);
|
||||
if ((_puzzleState->rotations[i] += inc) > 3) {
|
||||
_puzzleState->rotations[i] -= 4;
|
||||
}
|
||||
|
||||
drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the mouse is inside the pickup hotspot
|
||||
insideRect = _takeHotspot;
|
||||
insideRect.translate(screenHotspot.left, screenHotspot.top);
|
||||
|
||||
if (insideRect.contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Player has clicked, take the piece
|
||||
|
||||
// First, copy the graphic from the full drawSurface...
|
||||
if (!_useCustomPickUpTile) {
|
||||
_pickedUpPiece._drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
_pickedUpPiece._drawSurface.blitFrom(_drawSurface, _destRects[i], Common::Point());
|
||||
}
|
||||
|
||||
_pickedUpPiece.setVisible(true);
|
||||
_pickedUpPiece.setTransparent(true);
|
||||
_pickedUpPiece.pickUp();
|
||||
|
||||
// ...then change the data...
|
||||
_puzzleState->_pickedUpPieceID = _puzzleState->order[i];
|
||||
_puzzleState->_pickedUpPieceRot = _puzzleState->rotations[i];
|
||||
_puzzleState->order[i] = -1;
|
||||
_puzzleState->_pickedUpPieceLastPos = i;
|
||||
|
||||
// ...then clear the piece from the drawSurface
|
||||
drawPiece(i, 0);
|
||||
|
||||
g_nancy->_sound->playSound(_takeSound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Currently carrying a piece
|
||||
|
||||
// Check if the mouse is inside the drop hotspot
|
||||
insideRect = _dropHotspot;
|
||||
insideRect.translate(screenHotspot.left, screenHotspot.top);
|
||||
|
||||
if (insideRect.contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Player has clicked, drop the piece and pick up a new one
|
||||
|
||||
// Check if we should pick up a new piece
|
||||
if (_puzzleState->order[i] == -1) {
|
||||
// No, hide the picked up piece graphic
|
||||
_pickedUpPiece.setVisible(false);
|
||||
_puzzleState->_pickedUpPieceLastPos = -1;
|
||||
} else {
|
||||
// Yes, change the picked piece graphic
|
||||
if (!_useCustomPickUpTile) {
|
||||
_pickedUpPiece._drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
_pickedUpPiece._drawSurface.blitFrom(_drawSurface, _destRects[i], Common::Point());
|
||||
}
|
||||
|
||||
_pickedUpPiece.setVisible(true);
|
||||
_pickedUpPiece.setTransparent(true);
|
||||
}
|
||||
|
||||
SWAP<int8>(_puzzleState->order[i], _puzzleState->_pickedUpPieceID);
|
||||
SWAP<byte>(_puzzleState->rotations[i], _puzzleState->_pickedUpPieceRot);
|
||||
|
||||
// Draw the newly placed piece
|
||||
drawPiece(i, _puzzleState->rotations[i], _puzzleState->order[i]);
|
||||
|
||||
g_nancy->_sound->playSound(_dropSound);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pickedUpPiece.handleInput(input);
|
||||
|
||||
if (_puzzleState->_pickedUpPieceID == -1) {
|
||||
// No piece picked up, check the exit hotspot
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(_customCursorID != -1 ? (CursorManager::CursorType)_customCursorID : g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Player has clicked, exit
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RippedLetterPuzzle::drawPiece(const uint pos, const byte rotation, const int pieceID) {
|
||||
// Clear the selected position
|
||||
_drawSurface.fillRect(_destRects[pos], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
|
||||
// No piece, just clear
|
||||
if (pieceID == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create temporary ManagedSurfaces and call the custom rotation function
|
||||
Graphics::ManagedSurface srcSurf(_image, _srcRects[pieceID]);
|
||||
Graphics::ManagedSurface destSurf(_drawSurface, _destRects[pos]);
|
||||
GraphicsManager::rotateBlit(srcSurf, destSurf, rotation);
|
||||
}
|
||||
|
||||
bool RippedLetterPuzzle::checkOrder(bool useAlt) {
|
||||
auto ¤t = _puzzleState->order;
|
||||
auto &correct = useAlt ? _solveOrderAlt : _solveOrder;
|
||||
|
||||
for (uint i = 0; i < correct.size(); ++i) {
|
||||
bool foundCorrect = false;
|
||||
bool isDoubled = false;
|
||||
for (auto &d : _doubles) {
|
||||
for (byte e : d) {
|
||||
if (current[i] == e) {
|
||||
isDoubled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDoubled) {
|
||||
for (byte e : d) {
|
||||
if (correct[i] == e) {
|
||||
foundCorrect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundCorrect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDoubled) {
|
||||
if (current[i] != correct[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
99
engines/nancy/action/puzzle/rippedletterpuzzle.h
Normal file
99
engines/nancy/action/puzzle/rippedletterpuzzle.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_RIPPEDLETTERPUZZLE_H
|
||||
#define NANCY_ACTION_RIPPEDLETTERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct RippedLetterPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class RippedLetterPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForSound };
|
||||
enum RotationType { kRotationNone = 0, kRotation90 = 1, kRotation180 = 2 };
|
||||
RippedLetterPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~RippedLetterPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Array<Common::Rect> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
|
||||
Common::Rect _rotateHotspot;
|
||||
Common::Rect _takeHotspot;
|
||||
Common::Rect _dropHotspot;
|
||||
|
||||
RotationType _rotationType = kRotation90;
|
||||
|
||||
Common::Array<int8> _initOrder;
|
||||
Common::Array<byte> _initRotations;
|
||||
Common::Array<int8> _solveOrder;
|
||||
Common::Array<byte> _solveRotations;
|
||||
Common::Array<int8> _solveOrderAlt;
|
||||
Common::Array<byte> _solveRotationsAlt;
|
||||
Common::Array<Common::Array<byte>> _doubles;
|
||||
bool _useAltSolution = false;
|
||||
|
||||
bool _useCustomPickUpTile = false;
|
||||
Common::Rect _customPickUpTileSrc;
|
||||
|
||||
SoundDescription _takeSound;
|
||||
SoundDescription _dropSound;
|
||||
SoundDescription _rotateSound;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
int16 _customCursorID = -1;
|
||||
|
||||
Misc::MouseFollowObject _pickedUpPiece;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
SolveState _solveState = kNotSolved;
|
||||
RippedLetterPuzzleData *_puzzleState = nullptr;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "RippedLetterPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawPiece(const uint pos, const byte rotation, const int pieceID = -1);
|
||||
bool checkOrder(bool useAlt);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif //NANCY_ACTION_RIPPEDLETTERPUZZLE_H
|
||||
219
engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
Normal file
219
engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
/* 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 "common/random.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/rotatinglockpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void RotatingLockPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
}
|
||||
|
||||
void RotatingLockPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint numDials = stream.readUint16LE();
|
||||
|
||||
_srcRects.reserve(10);
|
||||
for (uint i = 0; i < 10; ++i) {
|
||||
_srcRects.push_back(Common::Rect());
|
||||
readRect(stream, _srcRects.back());
|
||||
}
|
||||
|
||||
_destRects.reserve(numDials);
|
||||
for (uint i = 0; i < numDials; ++i) {
|
||||
_destRects.push_back(Common::Rect());
|
||||
readRect(stream, _destRects.back());
|
||||
|
||||
if (i == 0) {
|
||||
_screenPosition = _destRects.back();
|
||||
} else {
|
||||
_screenPosition.extend(_destRects.back());
|
||||
}
|
||||
}
|
||||
|
||||
stream.skip((8 - numDials) * 16);
|
||||
|
||||
_upHotspots.reserve(numDials);
|
||||
for (uint i = 0; i < numDials; ++i) {
|
||||
_upHotspots.push_back(Common::Rect());
|
||||
readRect(stream, _upHotspots.back());
|
||||
}
|
||||
|
||||
_downHotspots.reserve(numDials);
|
||||
stream.skip((8 - numDials) * 16);
|
||||
|
||||
for (uint i = 0; i < numDials; ++i) {
|
||||
_downHotspots.push_back(Common::Rect());
|
||||
readRect(stream, _downHotspots.back());
|
||||
}
|
||||
|
||||
stream.skip((8 - numDials) * 16);
|
||||
|
||||
_correctSequence.reserve(numDials);
|
||||
for (uint i = 0; i < numDials; ++i) {
|
||||
_correctSequence.push_back(stream.readByte());
|
||||
}
|
||||
|
||||
stream.skip(8 - numDials);
|
||||
|
||||
_clickSound.readNormal(stream);
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void RotatingLockPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
for (uint i = 0; i < _correctSequence.size(); ++i) {
|
||||
_currentSequence.push_back(g_nancy->_randomSource->getRandomNumber(9));
|
||||
drawDial(i);
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_clickSound);
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
for (uint i = 0; i < _correctSequence.size(); ++i) {
|
||||
if (_currentSequence[i] != (int16)_correctSequence[i]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setEventFlag(_solveExitScene._flag);
|
||||
_solveSoundPlayTime = g_nancy->getTotalPlayTime() + _solveSoundDelay * 1000;
|
||||
_solveState = kPlaySound;
|
||||
// fall through
|
||||
case kPlaySound:
|
||||
if (g_nancy->getTotalPlayTime() <= _solveSoundPlayTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case kActionTrigger:
|
||||
g_nancy->_sound->stopSound(_clickSound);
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
|
||||
if (_solveState == kNotSolved) {
|
||||
_exitScene.execute();
|
||||
} else {
|
||||
NancySceneState.changeScene(_solveExitScene._sceneChange);
|
||||
}
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void RotatingLockPuzzle::handleInput(NancyInput &input) {
|
||||
if (_solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _upHotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_upHotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_clickSound);
|
||||
|
||||
_currentSequence[i] = ++_currentSequence[i] > 9 ? 0 : _currentSequence[i];
|
||||
drawDial(i);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _downHotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_downHotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_clickSound);
|
||||
|
||||
int8 n = _currentSequence[i];
|
||||
n = --n < 0 ? 9 : n;
|
||||
_currentSequence[i] = n;
|
||||
drawDial(i);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void RotatingLockPuzzle::drawDial(uint id) {
|
||||
Common::Point destPoint(_destRects[id].left - _screenPosition.left, _destRects[id].top - _screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _srcRects[_currentSequence[id]], destPoint);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
70
engines/nancy/action/puzzle/rotatinglockpuzzle.h
Normal file
70
engines/nancy/action/puzzle/rotatinglockpuzzle.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_ROTATINGLOCKPUZZLE_H
|
||||
#define NANCY_ACTION_ROTATINGLOCKPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class RotatingLockPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
|
||||
RotatingLockPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~RotatingLockPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Rect> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<Common::Rect> _upHotspots;
|
||||
Common::Array<Common::Rect> _downHotspots;
|
||||
Common::Array<byte> _correctSequence;
|
||||
Nancy::SoundDescription _clickSound;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
Nancy::SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<byte> _currentSequence;
|
||||
Time _solveSoundPlayTime;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "RotatingLockPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawDial(uint id);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_ROTATINGLOCKPUZZLE_H
|
||||
321
engines/nancy/action/puzzle/safedialpuzzle.cpp
Normal file
321
engines/nancy/action/puzzle/safedialpuzzle.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
/* 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 "common/random.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/safedialpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SafeDialPuzzle::init() {
|
||||
g_nancy->_resource->loadImage(_imageName1, _image1);
|
||||
g_nancy->_resource->loadImage(_imageName2, _image2);
|
||||
g_nancy->_resource->loadImage(_resetImageName, _resetImage);
|
||||
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::updateGraphics() {
|
||||
if (_animState == kSelect && (_state == kActionTrigger ? _nextAnim - 500 : _nextAnim) < g_nancy->getTotalPlayTime()) {
|
||||
_drawSurface.fillRect(_arrowDest, _drawSurface.getTransparentColor());
|
||||
_animState = kNone;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_animState == kSpin && _nextAnim < g_nancy->getTotalPlayTime()) {
|
||||
drawDialFrame(_current * (1 + _numInbetweens));
|
||||
_animState = kNone;
|
||||
}
|
||||
|
||||
if (_animState == kReset && _nextAnim < g_nancy->getTotalPlayTime()) {
|
||||
if (!_resetImageName.empty()) {
|
||||
_animState = kResetAnim;
|
||||
} else {
|
||||
_animState = kNone;
|
||||
_current = 0;
|
||||
drawDialFrame(_current);
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_resetSound);
|
||||
}
|
||||
|
||||
if (_animState == kResetAnim) {
|
||||
// Framerate-dependent animation. We're restricting the engine to ~60fps so it shouldn't be too fast
|
||||
_drawSurface.blitFrom(_resetImage, _resetDialSrcs[_current % _resetDialSrcs.size()], _dialDest);
|
||||
++_current;
|
||||
if (_current >= _resetDialSrcs.size() * _resetTurns) {
|
||||
_animState = kNone;
|
||||
_current = 0;
|
||||
drawDialFrame(_current);
|
||||
}
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName1);
|
||||
readFilename(stream, _imageName2);
|
||||
readFilename(stream, _resetImageName);
|
||||
|
||||
_numInbetweens = (!_imageName2.empty() ? 1 : 0);
|
||||
|
||||
uint16 num = 10;
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy4) {
|
||||
num = stream.readUint16LE();
|
||||
_enableWraparound = stream.readByte();
|
||||
}
|
||||
|
||||
readRect(stream, _dialDest);
|
||||
readRectArray(stream, _dialSrcs, num * (1 + _numInbetweens), 20);
|
||||
|
||||
readRect(stream, _resetDest);
|
||||
readRect(stream, _resetSrc);
|
||||
readRect(stream, _arrowDest);
|
||||
readRect(stream, _arrowSrc);
|
||||
|
||||
readRectArray(stream, _resetDialSrcs, 10);
|
||||
|
||||
_resetTurns = stream.readUint16LE();
|
||||
|
||||
uint16 solveSize = stream.readUint16LE();
|
||||
_correctSequence.resize(solveSize);
|
||||
for (uint i = 0; i < solveSize; ++i) {
|
||||
_correctSequence[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((10 - solveSize) * 2);
|
||||
|
||||
readRect(stream, _ccwHotspot);
|
||||
readRect(stream, _cwHotspot);
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy4) {
|
||||
_useMoveArrows = stream.readByte();
|
||||
}
|
||||
|
||||
if (_useMoveArrows) {
|
||||
// Swap the two hotspots
|
||||
Common::Rect temp = _cwHotspot;
|
||||
_cwHotspot = _ccwHotspot;
|
||||
_ccwHotspot = temp;
|
||||
}
|
||||
|
||||
_spinSound.readNormal(stream);
|
||||
_selectSound.readNormal(stream);
|
||||
_resetSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_spinSound);
|
||||
g_nancy->_sound->loadSound(_selectSound);
|
||||
g_nancy->_sound->loadSound(_resetSound);
|
||||
_current = 0;
|
||||
drawDialFrame(_current);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (!g_nancy->_sound->isSoundPlaying(_selectSound) && g_nancy->getTotalPlayTime() > _nextAnim) {
|
||||
if (_playerSequence == _correctSequence) {
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + 1000 * _solveSoundDelay;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (_nextAnim == 0) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (_nextAnim < g_nancy->getTotalPlayTime()) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_nextAnim = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_spinSound);
|
||||
g_nancy->_sound->stopSound(_selectSound);
|
||||
g_nancy->_sound->stopSound(_resetSound);
|
||||
|
||||
finishExecution();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _playerSequence == _correctSequence) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_ccwHotspot).contains(input.mousePos)) {
|
||||
if (!_enableWraparound && _current == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveLeft : CursorManager::kRotateCCW);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
|
||||
_animState != kReset && _animState != kResetAnim) {
|
||||
if (_current == 0) {
|
||||
_current = _dialSrcs.size() / (1 + _numInbetweens) - 1;
|
||||
} else {
|
||||
--_current;
|
||||
}
|
||||
|
||||
drawDialFrame(_current * (1 + _numInbetweens) + (_numInbetweens ? 1 : 0));
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
|
||||
|
||||
g_nancy->_sound->playSound(_spinSound);
|
||||
_animState = kSpin;
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_cwHotspot).contains(input.mousePos)) {
|
||||
if (!_enableWraparound && _current == (_dialSrcs.size() / (1 + _numInbetweens) - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_cursor->setCursorType(_useMoveArrows ? CursorManager::kMoveRight : CursorManager::kRotateCW);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
|
||||
_animState != kReset && _animState != kResetAnim) {
|
||||
drawDialFrame(_current * (1 + _numInbetweens) + 1);
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
|
||||
|
||||
if (_current == (_dialSrcs.size() / (1 + _numInbetweens)) - 1) {
|
||||
_current = 0;
|
||||
} else {
|
||||
++_current;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_spinSound);
|
||||
_animState = kSpin;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_nancy->_sound->isSoundPlaying(_selectSound) || _animState == kReset || _animState == kResetAnim || _nextAnim > g_nancy->getTotalPlayTime()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_arrowDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_selectSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_selectSound);
|
||||
pushSequence(_current);
|
||||
_drawSurface.blitFrom(_image1, _arrowSrc, _arrowDest);
|
||||
_animState = kSelect;
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + 500; // hardcoded
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_resetDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_resetSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_drawSurface.blitFrom(_image1, _resetSrc, _resetDest);
|
||||
g_nancy->_sound->playSound(_selectSound);
|
||||
_animState = kReset;
|
||||
_nextAnim = g_nancy->getTotalPlayTime() + 500; // hardcoded
|
||||
_current = 0;
|
||||
_playerSequence.clear();
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::drawDialFrame(uint frame) {
|
||||
if (frame >= _dialSrcs.size() / 2 && !_imageName2.empty()) {
|
||||
_drawSurface.blitFrom(_image2, _dialSrcs[frame], _dialDest);
|
||||
} else {
|
||||
_drawSurface.blitFrom(_image1, _dialSrcs[frame], _dialDest);
|
||||
}
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void SafeDialPuzzle::pushSequence(uint id) {
|
||||
if (!_useMoveArrows && id != 0) {
|
||||
// When the puzzle is set to use rotation cursors, the ids in the correct sequence are in reverse order
|
||||
id = (_dialSrcs.size() / (1 + _numInbetweens)) - id;
|
||||
}
|
||||
|
||||
_playerSequence.push_back(id);
|
||||
if (_playerSequence.size() > _correctSequence.size()) {
|
||||
_playerSequence.erase(_playerSequence.begin());
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
103
engines/nancy/action/puzzle/safedialpuzzle.h
Normal file
103
engines/nancy/action/puzzle/safedialpuzzle.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SAFEDIALPUZZLE_H
|
||||
#define NANCY_ACTION_SAFEDIALPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Handles the nancy3 safe puzzle with Chinese characters on the dial,
|
||||
// as well as nancy4's sextant puzzle
|
||||
class SafeDialPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
SafeDialPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SafeDialPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
enum AnimState { kNone, kSpin, kSelect, kReset, kResetAnim };
|
||||
Common::String getRecordTypeName() const override { return "SafeDialPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawDialFrame(uint frame);
|
||||
void pushSequence(uint id);
|
||||
|
||||
Common::Path _imageName1;
|
||||
Common::Path _imageName2;
|
||||
Common::Path _resetImageName;
|
||||
|
||||
bool _enableWraparound = true;
|
||||
|
||||
Common::Rect _dialDest;
|
||||
|
||||
Common::Array<Common::Rect> _dialSrcs;
|
||||
|
||||
Common::Rect _resetDest;
|
||||
Common::Rect _resetSrc;
|
||||
Common::Rect _arrowDest;
|
||||
Common::Rect _arrowSrc;
|
||||
|
||||
Common::Array<Common::Rect> _resetDialSrcs;
|
||||
|
||||
uint16 _resetTurns = 0;
|
||||
|
||||
Common::Array<uint16> _correctSequence;
|
||||
|
||||
Common::Rect _ccwHotspot;
|
||||
Common::Rect _cwHotspot;
|
||||
|
||||
bool _useMoveArrows = false;
|
||||
|
||||
SoundDescription _spinSound;
|
||||
SoundDescription _selectSound;
|
||||
SoundDescription _resetSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image1, _image2, _resetImage;
|
||||
|
||||
uint _numInbetweens = 1;
|
||||
|
||||
Common::Array<uint16> _playerSequence;
|
||||
bool _solved = false;
|
||||
AnimState _animState = kNone;
|
||||
uint32 _nextAnim = 0;
|
||||
uint16 _current = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SAFEDIALPUZZLE_H
|
||||
301
engines/nancy/action/puzzle/setplayerclock.cpp
Normal file
301
engines/nancy/action/puzzle/setplayerclock.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/* 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/action/puzzle/setplayerclock.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/ui/clock.h"
|
||||
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
SetPlayerClock::~SetPlayerClock() {
|
||||
Nancy::UI::Clock *clock = NancySceneState.getClock();
|
||||
if (clock) {
|
||||
clock->lockClock(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SetPlayerClock::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void SetPlayerClock::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRect(stream, _minutesDest);
|
||||
readRect(stream, _hoursDest);
|
||||
readRect(stream, _AMPMDest);
|
||||
readRect(stream, _timeButtonDest);
|
||||
readRect(stream, _alarmButtonDest);
|
||||
readRect(stream, _setButtonDest);
|
||||
readRect(stream, _cancelButtonDest);
|
||||
readRect(stream, _upButtonDest);
|
||||
readRect(stream, _downButtonDest);
|
||||
readRect(stream, _modeLightDest);
|
||||
|
||||
readRectArray(stream, _minutesSrc, 4);
|
||||
readRectArray(stream, _hoursSrc, 12);
|
||||
|
||||
readRect(stream, _AMSrc);
|
||||
readRect(stream, _PMSrc);
|
||||
readRect(stream, _timeButtonSrc);
|
||||
readRect(stream, _alarmButtonSrc);
|
||||
readRect(stream, _setButtonSrc);
|
||||
readRect(stream, _cancelButtonSrc);
|
||||
readRect(stream, _upButtonSrc);
|
||||
readRect(stream, _downButtonSrc);
|
||||
readRect(stream, _timeLightSrc);
|
||||
readRect(stream, _alarmLightSrc);
|
||||
|
||||
stream.skip(2);
|
||||
|
||||
_buttonSound.readNormal(stream);
|
||||
_alarmSetScene.readData(stream);
|
||||
_alarmSoundDelay = stream.readUint16LE();
|
||||
_alarmSetSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
}
|
||||
|
||||
void SetPlayerClock::execute() {
|
||||
switch (_state) {
|
||||
case kBegin: {
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
g_nancy->_sound->loadSound(_buttonSound);
|
||||
|
||||
_alarmHours = NancySceneState.getPlayerTime().getHours();
|
||||
|
||||
Nancy::UI::Clock *clock = NancySceneState.getClock();
|
||||
if (clock) {
|
||||
clock->lockClock(true);
|
||||
}
|
||||
|
||||
_state = kRun;
|
||||
}
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_alarmState == kTimeMode) {
|
||||
Time currentTime = NancySceneState.getPlayerTime();
|
||||
int8 hours = currentTime.getHours();
|
||||
int8 minutes = currentTime.getMinutes();
|
||||
|
||||
if (_clearButton && !g_nancy->_sound->isSoundPlaying(_buttonSound)) {
|
||||
_drawSurface.fillRect(_timeButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_modeLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _timeLightSrc, _modeLightDest);
|
||||
_clearButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_lastDrawnHours != hours || _lastDrawnMinutes / 15 != minutes / 15) {
|
||||
drawTime(currentTime.getHours(), currentTime.getMinutes());
|
||||
_lastDrawnHours = hours;
|
||||
_lastDrawnMinutes = minutes;
|
||||
}
|
||||
} else {
|
||||
if (_clearButton && !g_nancy->_sound->isSoundPlaying(_buttonSound)) {
|
||||
_drawSurface.fillRect(_alarmButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_upButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_downButtonDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_modeLightDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.blitFrom(_image, _alarmLightSrc, _modeLightDest);
|
||||
drawTime(_alarmHours, 0);
|
||||
_clearButton = false;
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_buttonSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_clearButton) {
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
_drawSurface.fillRect(_setButtonDest, _drawSurface.getTransparentColor());
|
||||
_clearButton = false;
|
||||
}
|
||||
|
||||
if (_alarmState == kWait) {
|
||||
if (_sceneChangeTime != 0) {
|
||||
// Alarm has been set, wait for timer
|
||||
if (g_system->getMillis() > _sceneChangeTime) {
|
||||
_sceneChangeTime = 0;
|
||||
g_nancy->_sound->loadSound(_alarmSetSound);
|
||||
g_nancy->_sound->playSound(_alarmSetSound);
|
||||
}
|
||||
}
|
||||
if (_sceneChangeTime == 0) {
|
||||
if (!g_nancy->_sound->isSoundPlaying(_alarmSetSound)) {
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
g_nancy->_sound->stopSound(_alarmSetSound);
|
||||
NancySceneState.setPlayerTime(_alarmHours * 3600000, false);
|
||||
_alarmSetScene.execute();
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Cancel button pressed, go to exit scene
|
||||
g_nancy->_sound->stopSound(_buttonSound);
|
||||
_exitScene.execute();
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetPlayerClock::handleInput(NancyInput &input) {
|
||||
if (_alarmState == kWait) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel button is active in both time and alarm mode
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_cancelButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Cancel button pressed
|
||||
_drawSurface.blitFrom(_image, _cancelButtonSrc, _cancelButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
|
||||
_state = kActionTrigger;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_alarmState == kTimeMode) {
|
||||
// Alarm button is active only in time mode
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_alarmButtonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Alarm button pressed
|
||||
_drawSurface.blitFrom(_image, _alarmButtonSrc, _alarmButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_lastDrawnHours = _lastDrawnMinutes = -1;
|
||||
|
||||
_alarmState = kAlarmMode;
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_timeButtonDest).contains(input.mousePos)) {
|
||||
// Time button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Alarm button pressed
|
||||
_drawSurface.blitFrom(_image, _timeButtonSrc, _timeButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
|
||||
_alarmState = kTimeMode;
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_upButtonDest).contains(input.mousePos)) {
|
||||
// Up button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Up button pressed
|
||||
_drawSurface.blitFrom(_image, _upButtonSrc, _upButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_alarmHours = _alarmHours + 1 > 23 ? 0 : _alarmHours + 1;
|
||||
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_downButtonDest).contains(input.mousePos)) {
|
||||
// Down button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Down button pressed
|
||||
_drawSurface.blitFrom(_image, _downButtonSrc, _downButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_alarmHours = _alarmHours - 1 < 0 ? 23 : _alarmHours - 1;
|
||||
|
||||
_clearButton = true;
|
||||
return;
|
||||
}
|
||||
} else if (NancySceneState.getViewport().convertViewportToScreen(_setButtonDest).contains(input.mousePos)) {
|
||||
// Set button is active only in alarm mode
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Down button pressed
|
||||
_drawSurface.blitFrom(_image, _setButtonSrc, _setButtonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
|
||||
_clearButton = true;
|
||||
_state = kActionTrigger;
|
||||
_alarmState = kWait;
|
||||
_sceneChangeTime = g_system->getMillis() + (_alarmSoundDelay * 1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetPlayerClock::drawTime(uint16 hours, uint16 minutes) {
|
||||
_drawSurface.fillRect(_hoursDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_minutesDest, _drawSurface.getTransparentColor());
|
||||
_drawSurface.fillRect(_AMPMDest, _drawSurface.getTransparentColor());
|
||||
|
||||
_drawSurface.blitFrom(_image, _hoursSrc[(hours - 1 < 0 ? 11 : hours - 1) % 12], _hoursDest);
|
||||
_drawSurface.blitFrom(_image, _minutesSrc[minutes / 15], _minutesDest);
|
||||
_drawSurface.blitFrom(_image, hours / 12 == 0 ? _AMSrc : _PMSrc, _AMPMDest);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
95
engines/nancy/action/puzzle/setplayerclock.h
Normal file
95
engines/nancy/action/puzzle/setplayerclock.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SETPLAYERCLOCK_H
|
||||
#define NANCY_ACTION_SETPLAYERCLOCK_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Action record implementing an alarm clock. First used in nancy3
|
||||
class SetPlayerClock : public RenderActionRecord {
|
||||
public:
|
||||
enum AlarmState { kTimeMode, kAlarmMode, kWait };
|
||||
SetPlayerClock() : RenderActionRecord(7) {}
|
||||
virtual ~SetPlayerClock();
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SetPlayerClock"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawTime(uint16 hours, uint16 minutes);
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Rect _minutesDest;
|
||||
Common::Rect _hoursDest;
|
||||
Common::Rect _AMPMDest;
|
||||
Common::Rect _timeButtonDest;
|
||||
Common::Rect _alarmButtonDest;
|
||||
Common::Rect _setButtonDest;
|
||||
Common::Rect _cancelButtonDest;
|
||||
Common::Rect _upButtonDest;
|
||||
Common::Rect _downButtonDest;
|
||||
Common::Rect _modeLightDest;
|
||||
|
||||
Common::Array<Common::Rect> _minutesSrc;
|
||||
Common::Array<Common::Rect> _hoursSrc;
|
||||
Common::Rect _AMSrc;
|
||||
Common::Rect _PMSrc;
|
||||
Common::Rect _timeButtonSrc;
|
||||
Common::Rect _alarmButtonSrc;
|
||||
Common::Rect _setButtonSrc;
|
||||
Common::Rect _cancelButtonSrc;
|
||||
Common::Rect _upButtonSrc;
|
||||
Common::Rect _downButtonSrc;
|
||||
Common::Rect _timeLightSrc;
|
||||
Common::Rect _alarmLightSrc;
|
||||
|
||||
SoundDescription _buttonSound;
|
||||
SceneChangeWithFlag _alarmSetScene;
|
||||
uint16 _alarmSoundDelay = 0;
|
||||
SoundDescription _alarmSetSound; // NO SOUND in MHM
|
||||
SceneChangeWithFlag _exitScene;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int8 _lastDrawnHours = -1;
|
||||
int8 _lastDrawnMinutes = -1;
|
||||
int8 _alarmHours = -1;
|
||||
bool _clearButton = true;
|
||||
Time _sceneChangeTime;
|
||||
|
||||
AlarmState _alarmState = kTimeMode;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SETPLAYERCLOCK_H
|
||||
299
engines/nancy/action/puzzle/sliderpuzzle.cpp
Normal file
299
engines/nancy/action/puzzle/sliderpuzzle.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/sliderpuzzle.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SliderPuzzle::init() {
|
||||
_drawSurface.create(_screenPosition.width(), _screenPosition.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
|
||||
setTransparent(true);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
}
|
||||
|
||||
void SliderPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (SliderPuzzleData *)NancySceneState.getPuzzleData(SliderPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_width = stream.readUint16LE();
|
||||
_height = stream.readUint16LE();
|
||||
|
||||
_srcRects.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
readRectArray(stream, _srcRects[y], _width, 6);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 16);
|
||||
|
||||
_destRects.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
readRectArray(stream, _destRects[y], _width, 6);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 16);
|
||||
|
||||
_screenPosition = _destRects[0][0];
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
_screenPosition.extend(_destRects[y][x]);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_nancy->getGameType() >= kGameTypeNancy9) {
|
||||
_retainState = stream.readByte();
|
||||
|
||||
_startTileOrder.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
_startTileOrder[y].resize(_width);
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
_startTileOrder[y][x] = stream.readSint16LE();
|
||||
}
|
||||
stream.skip((6 - _width) * 2);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 2);
|
||||
} else {
|
||||
auto *spuzData = GetEngineData(SPUZ);
|
||||
assert(spuzData);
|
||||
_startTileOrder = spuzData->tileOrder[NancySceneState.getDifficulty()];
|
||||
}
|
||||
|
||||
_correctTileOrder.resize(_height);
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
_correctTileOrder[y].resize(_width);
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
_correctTileOrder[y][x] = stream.readSint16LE();
|
||||
}
|
||||
stream.skip((6 - _width) * 2);
|
||||
}
|
||||
stream.skip((6 - _height) * 6 * 2);
|
||||
|
||||
_clickSound.readNormal(stream);
|
||||
_solveExitScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void SliderPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (SliderPuzzleData *)NancySceneState.getPuzzleData(SliderPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
if (!_puzzleState->playerHasTriedPuzzle || !_retainState) {
|
||||
_puzzleState->playerTileOrder = _startTileOrder;
|
||||
_puzzleState->playerHasTriedPuzzle = true;
|
||||
}
|
||||
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
drawTile(_puzzleState->playerTileOrder[y][x], x, y);
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
g_nancy->_sound->loadSound(_clickSound);
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
if (_puzzleState->playerTileOrder[y][x] != _correctTileOrder[y][x]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
_exitScene.execute();
|
||||
break;
|
||||
case kWaitForSound:
|
||||
_solveExitScene.execute();
|
||||
_puzzleState->playerHasTriedPuzzle = false;
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_clickSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void SliderPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int currentTileX = -1;
|
||||
int currentTileY = -1;
|
||||
uint direction = 0;
|
||||
for (uint y = 0; y < _height; ++y) {
|
||||
bool shouldBreak = false;
|
||||
for (uint x = 0; x < _width; ++x) {
|
||||
if (x > 0 && _puzzleState->playerTileOrder[y][x - 1] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kLeft;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
} else if ((int)x < _width - 1 && _puzzleState->playerTileOrder[y][x + 1] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kRight;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
} else if (y > 0 && _puzzleState->playerTileOrder[y - 1][x] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kUp;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
} else if ((int)y < _height - 1 && _puzzleState->playerTileOrder[y + 1][x] < 0) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[y][x]).contains(input.mousePos)) {
|
||||
currentTileX = x;
|
||||
currentTileY = y;
|
||||
direction = kDown;
|
||||
shouldBreak = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldBreak) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTileX != -1) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_clickSound);
|
||||
switch (direction) {
|
||||
case kUp: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX, currentTileY - 1);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY - 1][currentTileX] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
case kDown: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX, currentTileY + 1);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY + 1][currentTileX] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
case kLeft: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX - 1, currentTileY);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX - 1] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
case kRight: {
|
||||
uint curTileID = _puzzleState->playerTileOrder[currentTileY][currentTileX];
|
||||
drawTile(curTileID, currentTileX + 1, currentTileY);
|
||||
undrawTile(currentTileX, currentTileY);
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX + 1] = curTileID;
|
||||
_puzzleState->playerTileOrder[currentTileY][currentTileX] = -10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SliderPuzzle::drawTile(int tileID, uint posX, uint posY) {
|
||||
if (tileID < 0) {
|
||||
undrawTile(posX, posY);
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point destPoint(_destRects[posY][posX].left - _screenPosition.left, _destRects[posY][posX].top - _screenPosition.top);
|
||||
_drawSurface.blitFrom(_image, _srcRects[tileID / _height][tileID % _width], destPoint);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void SliderPuzzle::undrawTile(uint posX, uint posY) {
|
||||
Common::Rect bounds = _destRects[posY][posX];
|
||||
bounds.translate(-_screenPosition.left, -_screenPosition.top);
|
||||
_drawSurface.fillRect(bounds, g_nancy->_graphics->getTransColor());
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
77
engines/nancy/action/puzzle/sliderpuzzle.h
Normal file
77
engines/nancy/action/puzzle/sliderpuzzle.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SLIDERPUZZLE_H
|
||||
#define NANCY_ACTION_SLIDERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct SPUZ;
|
||||
struct SliderPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class SliderPuzzle: public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForSound };
|
||||
SliderPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SliderPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
SliderPuzzleData *_puzzleState = nullptr;
|
||||
|
||||
Common::Path _imageName;
|
||||
uint16 _width = 0;
|
||||
uint16 _height = 0;
|
||||
Common::Array<Common::Array<Common::Rect>> _srcRects;
|
||||
Common::Array<Common::Array<Common::Rect>> _destRects;
|
||||
Common::Array<Common::Array<int16>> _startTileOrder;
|
||||
Common::Array<Common::Array<int16>> _correctTileOrder;
|
||||
SoundDescription _clickSound;
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
bool _retainState = true;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SliderPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawTile(int tileID, uint posX, uint posY);
|
||||
void undrawTile(uint posX, uint posY);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SLIDERPUZZLE_H
|
||||
311
engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
Normal file
311
engines/nancy/action/puzzle/soundequalizerpuzzle.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
/* 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/action/puzzle/soundequalizerpuzzle.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/ui/scrollbar.h"
|
||||
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class ViewportScrollbar : public UI::Scrollbar {
|
||||
public:
|
||||
ViewportScrollbar(uint16 zOrder, const Common::Rect &srcBounds, Graphics::ManagedSurface &srcSurf, const Common::Point &topPosition, uint16 scrollDistance, bool isVertical = true) :
|
||||
Scrollbar(zOrder, srcBounds, srcSurf, topPosition, scrollDistance, isVertical) {}
|
||||
virtual ~ViewportScrollbar() = default;
|
||||
|
||||
bool handleInput(NancyInput &input) {
|
||||
if (_screenPosition.contains(input.mousePos)) {
|
||||
input.input &= (~NancyInput::kRightMouseButtonUp);
|
||||
|
||||
Scrollbar::handleInput(input);
|
||||
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
SoundEqualizerPuzzle::~SoundEqualizerPuzzle() {
|
||||
for (auto *scrollbar : _sliders) {
|
||||
delete scrollbar;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
const VIEW *viewportData = (const VIEW *)g_nancy->getEngineData("VIEW");
|
||||
assert(viewportData);
|
||||
Common::Rect vpPos = viewportData->screenPosition;
|
||||
|
||||
if (_puzzleState->sliderValues[0] == 255) {
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
_puzzleState->sliderValues[i] = _sliderInitialPositions[i];
|
||||
}
|
||||
}
|
||||
|
||||
_sliders.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
Common::Point screenDest(_sliderX[i], _sliderYMax[i] - (_sliderSrc.height() / 2));
|
||||
screenDest.x += vpPos.left;
|
||||
screenDest.y += vpPos.top;
|
||||
_sliders[i] = new ViewportScrollbar( 8,
|
||||
_sliderSrc,
|
||||
_image,
|
||||
screenDest,
|
||||
_sliderYMin[i] - _sliderYMax[i]);
|
||||
_sliders[i]->init();
|
||||
_sliders[i]->setPosition((float)(100 - _puzzleState->sliderValues[i]) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::registerGraphics() {
|
||||
for (auto *scrollbar : _sliders) {
|
||||
scrollbar->registerGraphics();
|
||||
}
|
||||
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (SoundEqualizerPuzzleData *)NancySceneState.getPuzzleData(SoundEqualizerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
uint16 difficulty = NancySceneState.getDifficulty();
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
readRect(stream, _buttonSrc);
|
||||
readRect(stream, _buttonDest);
|
||||
readRect(stream, _sliderSrc);
|
||||
|
||||
_sliderX.resize(6);
|
||||
_sliderYMin.resize(6);
|
||||
_sliderYMax.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
_sliderX[i] = stream.readUint16LE();
|
||||
_sliderYMin[i] = stream.readUint16LE();
|
||||
_sliderYMax[i] = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRect(stream, _lightSrc);
|
||||
|
||||
_lightDests.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
readRectArray(stream, _lightDests[i], 10);
|
||||
}
|
||||
|
||||
_sliderInitialPositions.resize(6);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
// Only read the data for the current difficulty and skip over the rest
|
||||
if (i == difficulty) {
|
||||
for (uint j = 0; j < 6; ++j) {
|
||||
_sliderInitialPositions[j] = stream.readUint16LE();
|
||||
}
|
||||
} else {
|
||||
stream.skip(12);
|
||||
}
|
||||
}
|
||||
|
||||
_sounds.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_sounds[i].readNormal(stream);
|
||||
}
|
||||
|
||||
_minVolume.resize(3);
|
||||
_maxVolume.resize(3);
|
||||
_minRate.resize(3);
|
||||
_maxRate.resize(3);
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
// Only read the data for the current difficulty and skip over the rest
|
||||
if (i == difficulty) {
|
||||
for (uint j = 0; j < 3; ++j) {
|
||||
_minVolume[j] = stream.readUint16LE();
|
||||
_maxVolume[j] = stream.readUint16LE();
|
||||
_minRate[j] = stream.readUint32LE();
|
||||
_maxRate[j] = stream.readUint32LE();
|
||||
}
|
||||
} else {
|
||||
stream.skip(12 * 3);
|
||||
}
|
||||
}
|
||||
|
||||
_solveChannelID = stream.readUint16LE();
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
// Only read the data for the current difficulty and skip over the rest
|
||||
if (i == difficulty) {
|
||||
_solveMinVolume = stream.readUint16LE();
|
||||
_solveMaxVolume = stream.readUint16LE();
|
||||
_solveMinRate = stream.readUint32LE();
|
||||
_solveMaxRate = stream.readUint32LE();
|
||||
} else {
|
||||
stream.skip(12);
|
||||
}
|
||||
}
|
||||
|
||||
_exitScene.readData(stream);
|
||||
stream.skip(2);
|
||||
_exitSound.readNormal(stream);
|
||||
|
||||
_solveFlag.label = stream.readSint16LE();
|
||||
_solveFlag.flag = stream.readByte();
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::execute() {
|
||||
switch(_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (SoundEqualizerPuzzleData *)NancySceneState.getPuzzleData(SoundEqualizerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
g_nancy->_sound->loadSound(_sounds[i]);
|
||||
g_nancy->_sound->playSound(_sounds[i]);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
updateSlider(i);
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
break;
|
||||
case kRun:
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (g_nancy->_sound->isSoundPlaying(_exitSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
g_nancy->_sound->stopSound(_sounds[i]);
|
||||
}
|
||||
|
||||
NancySceneState.changeScene(_exitScene);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state == kActionTrigger) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
return;
|
||||
} else if (_state == kBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_buttonDest).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Exit button pressed
|
||||
_drawSurface.blitFrom(_image, _buttonSrc, _buttonDest);
|
||||
_needsRedraw = true;
|
||||
|
||||
g_nancy->_sound->loadSound(_exitSound);
|
||||
g_nancy->_sound->playSound(_exitSound);
|
||||
|
||||
_state = kActionTrigger;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
if (_sliders[i]->handleInput(input)) {
|
||||
updateSlider(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEqualizerPuzzle::updateSlider(uint sliderID) {
|
||||
float sliderVal = 1 - _sliders[sliderID]->getPos();
|
||||
_puzzleState->sliderValues[sliderID] = sliderVal * 100;
|
||||
|
||||
if (sliderID < 3) {
|
||||
// First three sliders change pitch, except when the slider
|
||||
// controls the "solve" sound; in that case it does nothing
|
||||
if (sliderID != _solveChannelID) {
|
||||
g_nancy->_sound->setRate(_sounds[sliderID],
|
||||
_minRate[sliderID] + (_maxRate[sliderID] - _minRate[sliderID]) * sliderVal);
|
||||
}
|
||||
} else {
|
||||
// Other three sliders change volume;
|
||||
// "solve" sound slider behaves as an on/off switch
|
||||
if (sliderID - 3 != _solveChannelID) {
|
||||
g_nancy->_sound->setVolume(_sounds[sliderID - 3],
|
||||
_minVolume[sliderID - 3] + (_maxVolume[sliderID - 3] - _minVolume[sliderID - 3]) * sliderVal);
|
||||
} else {
|
||||
if (sliderVal * 100 >= _solveMinVolume && sliderVal * 100 <= _solveMaxVolume) {
|
||||
g_nancy->_sound->setVolume(_sounds[sliderID - 3], _maxVolume[sliderID - 3]);
|
||||
|
||||
// Since the rate for the "solve" sound never actually changes,
|
||||
// we only need the volume to be correct.
|
||||
NancySceneState.setEventFlag(_solveFlag);
|
||||
} else {
|
||||
g_nancy->_sound->setVolume(_sounds[sliderID - 3], _minVolume[sliderID - 3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint numLights = sliderVal * 10;
|
||||
|
||||
if (numLights < 10) {
|
||||
Common::Rect clear = _lightDests[sliderID][numLights];
|
||||
clear.extend(_lightDests[sliderID][9]);
|
||||
|
||||
_drawSurface.fillRect(clear, _drawSurface.getTransparentColor());
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < numLights; ++i) {
|
||||
_drawSurface.blitFrom(_image, _lightSrc, _lightDests[sliderID][i]);
|
||||
|
||||
_needsRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
99
engines/nancy/action/puzzle/soundequalizerpuzzle.h
Normal file
99
engines/nancy/action/puzzle/soundequalizerpuzzle.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SOUNDEQUALIZERPUZZLE_H
|
||||
#define NANCY_ACTION_SOUNDEQUALIZERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace UI {
|
||||
class Scrollbar;
|
||||
}
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct SoundEqualizerPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class ViewportScrollbar;
|
||||
|
||||
class SoundEqualizerPuzzle: public RenderActionRecord {
|
||||
public:
|
||||
SoundEqualizerPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SoundEqualizerPuzzle();
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
Common::Rect _buttonSrc;
|
||||
Common::Rect _buttonDest;
|
||||
Common::Rect _sliderSrc;
|
||||
|
||||
Common::Array<uint16> _sliderX;
|
||||
Common::Array<uint16> _sliderYMin;
|
||||
Common::Array<uint16> _sliderYMax;
|
||||
|
||||
Common::Rect _lightSrc;
|
||||
Common::Array<Common::Array<Common::Rect>> _lightDests;
|
||||
|
||||
Common::Array<uint16> _sliderInitialPositions;
|
||||
|
||||
Common::Array<SoundDescription> _sounds;
|
||||
|
||||
Common::Array<uint16> _minVolume;
|
||||
Common::Array<uint16> _maxVolume;
|
||||
Common::Array<uint16> _minRate;
|
||||
Common::Array<uint16> _maxRate;
|
||||
|
||||
uint16 _solveChannelID = 0;
|
||||
uint16 _solveMinVolume = 0;
|
||||
uint16 _solveMaxVolume = 0;
|
||||
uint16 _solveMinRate = 0;
|
||||
uint16 _solveMaxRate = 0;
|
||||
|
||||
SceneChangeDescription _exitScene;
|
||||
SoundDescription _exitSound;
|
||||
|
||||
FlagDescription _solveFlag;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<ViewportScrollbar *> _sliders;
|
||||
|
||||
SoundEqualizerPuzzleData *_puzzleState = nullptr;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SoundEqualizerPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void updateSlider(uint sliderID);
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SOUNDEQUALIZERPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/soundmatchpuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/soundmatchpuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/soundmatchpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SoundMatchPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void SoundMatchPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Nancy 9 Whale sounds puzzle");
|
||||
NancySceneState.setEventFlag(436, g_nancy->_true); // EV_Solved_Whale_Call
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 2936;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void SoundMatchPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void SoundMatchPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/soundmatchpuzzle.h
Normal file
51
engines/nancy/action/puzzle/soundmatchpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SOUNDMATCHPUZZLE_H
|
||||
#define NANCY_ACTION_SOUNDMATCHPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Whale sound matching puzzle in Nancy 9
|
||||
|
||||
class SoundMatchPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
SoundMatchPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SoundMatchPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SoundMatchPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SOUNDMATCHPUZZLE_H
|
||||
301
engines/nancy/action/puzzle/spigotpuzzle.cpp
Normal file
301
engines/nancy/action/puzzle/spigotpuzzle.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/* 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/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/spigotpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void SpigotPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void SpigotPuzzle::updateGraphics() {
|
||||
if (_pushedButtonID != -1) {
|
||||
if (g_nancy->getTotalPlayTime() >= _nextAnimTime) {
|
||||
_animatingLetterID = _pushedButtonID;
|
||||
_drawSurface.fillRect(_buttonDests[_pushedButtonID], _drawSurface.getTransparentColor());
|
||||
_pushedButtonID = -1;
|
||||
_nextAnimTime = 0;
|
||||
|
||||
uint numSpins = _numSpins[_numPulls[_animatingLetterID] - 1];
|
||||
_frameID = numSpins * _numInbetweens;
|
||||
}
|
||||
}
|
||||
|
||||
if (_animatingSpigotID != -1) {
|
||||
uint32 curTime = g_nancy->getTotalPlayTime();
|
||||
if (curTime >= _nextAnimTime) {
|
||||
if (_nextAnimTime == 0) {
|
||||
_nextAnimTime = curTime + 100;
|
||||
} else {
|
||||
_nextAnimTime += 100;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_frameID != _spigotAnimSrcs[_animatingSpigotID].size()) {
|
||||
_drawSurface.blitFrom(_image, _spigotAnimSrcs[_animatingSpigotID][_frameID], _spigotDests[_animatingSpigotID]);
|
||||
_needsRedraw = true;
|
||||
|
||||
++_frameID;
|
||||
} else {
|
||||
// Increment the number and end the animation
|
||||
_numPulls[_animatingSpigotID] = MIN<uint16>(_numPulls[_animatingSpigotID] + 1, 6);
|
||||
_drawSurface.blitFrom(_image, _digitSrcs[_animatingSpigotID][_numPulls[_animatingSpigotID]], _digitDests[_animatingSpigotID]);
|
||||
|
||||
// Also, clear the last drawn spigot frame
|
||||
_drawSurface.fillRect(_spigotDests[_animatingSpigotID], _drawSurface.getTransparentColor());
|
||||
|
||||
_needsRedraw = true;
|
||||
_animatingSpigotID = -1;
|
||||
_frameID = 0;
|
||||
_nextAnimTime = 0;
|
||||
}
|
||||
} else if (_animatingLetterID != -1) {
|
||||
uint32 curTime = g_nancy->getTotalPlayTime();
|
||||
if (curTime >= _nextAnimTime) {
|
||||
if (_nextAnimTime == 0) {
|
||||
_nextAnimTime = curTime + _letterTime * 200;
|
||||
} else {
|
||||
_nextAnimTime += _letterTime * 200;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_frameID != 0) {
|
||||
if (++_currentAnimOrder[_animatingLetterID] >= _numLetters * _numInbetweens) {
|
||||
_currentAnimOrder[_animatingLetterID] = 0;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_letterSound);
|
||||
_drawSurface.blitFrom(_image, _letterSrcs[_animatingLetterID][_currentAnimOrder[_animatingLetterID]], _letterDests[_animatingLetterID]);
|
||||
_needsRedraw = true;
|
||||
|
||||
--_frameID;
|
||||
} else {
|
||||
// Clear the number
|
||||
_numPulls[_animatingLetterID] = 0;
|
||||
_drawSurface.fillRect(_digitDests[_animatingLetterID], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
|
||||
// Set the order at the end of the animation to avoid "solving" while animating
|
||||
_currentOrder[_animatingLetterID] = _currentAnimOrder[_animatingLetterID] / _numInbetweens;
|
||||
_animatingLetterID = -1;
|
||||
_nextAnimTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpigotPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numSpigots = stream.readUint16LE();
|
||||
_numLetters = stream.readUint16LE();
|
||||
_numInbetweens = stream.readUint16LE();
|
||||
|
||||
_startOrder.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_startOrder[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 2);
|
||||
|
||||
_numSpins.resize(6);
|
||||
for (uint i = 0; i < 6; ++i) {
|
||||
_numSpins[i] = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRectArray(stream, _spigotDests, _numSpigots, 6);
|
||||
readRectArray(stream, _spigotHotspots, _numSpigots, 6);
|
||||
readRectArray(stream, _letterDests, _numSpigots, 6);
|
||||
readRectArray(stream, _digitDests, _numSpigots, 6);
|
||||
readRectArray(stream, _buttonDests, _numSpigots, 6);
|
||||
|
||||
uint16 numSpigotAnimationFrames = stream.readUint16LE();
|
||||
_spigotAnimSrcs.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_spigotAnimSrcs[i].resize(numSpigotAnimationFrames);
|
||||
|
||||
uint32 x = stream.readUint32LE();
|
||||
uint32 y = stream.readUint32LE();
|
||||
uint16 deltaX = stream.readUint16LE();
|
||||
uint16 height = stream.readUint16LE() + 1;
|
||||
|
||||
for (uint j = 0; j < numSpigotAnimationFrames; ++j) {
|
||||
_spigotAnimSrcs[i][j] = Common::Rect(x + j * deltaX, y, x + j * deltaX + deltaX, y + height);
|
||||
}
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 12);
|
||||
|
||||
_digitSrcs.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
readRectArray(stream, _digitSrcs[i], 7);
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 7 * 16);
|
||||
|
||||
readRectArray(stream, _buttonSrcs, _numSpigots, 6);
|
||||
|
||||
_letterSrcs.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
readRectArray(stream, _letterSrcs[i], _numLetters * _numInbetweens, 7 * 2);
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 7 * 2 * 16);
|
||||
|
||||
_letterTime = stream.readUint16LE();
|
||||
|
||||
_correctOrder.resize(_numSpigots);
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_correctOrder[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((6 - _numSpigots) * 2);
|
||||
|
||||
_buttonSound.readNormal(stream);
|
||||
_letterSound.readNormal(stream);
|
||||
_spigotSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void SpigotPuzzle::execute() {
|
||||
switch(_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_buttonSound);
|
||||
g_nancy->_sound->loadSound(_letterSound);
|
||||
g_nancy->_sound->loadSound(_spigotSound);
|
||||
|
||||
_currentOrder = _startOrder;
|
||||
_currentAnimOrder.resize(_currentOrder.size());
|
||||
for (uint i = 0; i < _currentAnimOrder.size(); ++i) {
|
||||
_currentAnimOrder[i] = _currentOrder[i] * _numInbetweens;
|
||||
}
|
||||
_numPulls.resize(_numSpigots, 0);
|
||||
|
||||
// Draw the start letters, in case the background ones are different
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
_drawSurface.blitFrom(_image, _letterSrcs[i][_currentAnimOrder[i]], _letterDests[i]);
|
||||
}
|
||||
_needsRedraw = true;
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (_currentOrder == _correctOrder) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_solved) {
|
||||
// Sound delay not used
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpigotPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun || _animatingSpigotID != -1 || _animatingLetterID != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect vpScreenPos = NancySceneState.getViewport().convertViewportToScreen(_screenPosition);
|
||||
if (!vpScreenPos.contains(input.mousePos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point mousePos = input.mousePos;
|
||||
mousePos -= { vpScreenPos.left, vpScreenPos.top };
|
||||
|
||||
if (_exitHotspot.contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _numSpigots; ++i) {
|
||||
if (_spigotHotspots[i].contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_spigotSound);
|
||||
_animatingSpigotID = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_numPulls[i] && _buttonDests[i].contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_buttonSound);
|
||||
_drawSurface.blitFrom(_image, _buttonSrcs[i], _buttonDests[i]);
|
||||
_needsRedraw = true;
|
||||
_pushedButtonID = i;
|
||||
_nextAnimTime = g_nancy->getTotalPlayTime() + 250;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
99
engines/nancy/action/puzzle/spigotpuzzle.h
Normal file
99
engines/nancy/action/puzzle/spigotpuzzle.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SPIGOTPUZZLE_H
|
||||
#define NANCY_ACTION_SPIGOTPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// A puzzle in nancy7 where you pull spigots to input a password
|
||||
class SpigotPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
SpigotPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~SpigotPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "SpigotPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint _numSpigots = 0;
|
||||
uint _numLetters = 0;
|
||||
uint _numInbetweens = 0;
|
||||
|
||||
Common::Array<uint16> _startOrder;
|
||||
Common::Array<uint16> _numSpins;
|
||||
|
||||
Common::Array<Common::Rect> _spigotDests;
|
||||
Common::Array<Common::Rect> _spigotHotspots;
|
||||
Common::Array<Common::Rect> _letterDests;
|
||||
Common::Array<Common::Rect> _digitDests;
|
||||
Common::Array<Common::Rect> _buttonDests;
|
||||
|
||||
Common::Array<Common::Array<Common::Rect>> _spigotAnimSrcs;
|
||||
Common::Array<Common::Array<Common::Rect>> _digitSrcs;
|
||||
Common::Array<Common::Rect> _buttonSrcs;
|
||||
Common::Array<Common::Array<Common::Rect>> _letterSrcs;
|
||||
|
||||
uint16 _letterTime = 0; // 1 unit = 200ms
|
||||
Common::Array<uint16> _correctOrder;
|
||||
|
||||
SoundDescription _buttonSound;
|
||||
SoundDescription _letterSound;
|
||||
SoundDescription _spigotSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
Common::Array<uint16> _currentOrder;
|
||||
Common::Array<uint16> _currentAnimOrder;
|
||||
Common::Array<uint16> _numPulls;
|
||||
|
||||
int _animatingSpigotID = -1;
|
||||
int _animatingLetterID = -1;
|
||||
int _pushedButtonID = -1;
|
||||
uint _frameID = 0;
|
||||
uint32 _nextAnimTime = 0;
|
||||
|
||||
bool _solved = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SPIGOTPUZZLE_H
|
||||
453
engines/nancy/action/puzzle/tangrampuzzle.cpp
Normal file
453
engines/nancy/action/puzzle/tangrampuzzle.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
/* 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/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/tangrampuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
TangramPuzzle::~TangramPuzzle() {
|
||||
delete[] _zBuffer;
|
||||
}
|
||||
|
||||
void TangramPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_tileImageName, _tileImage);
|
||||
g_nancy->_resource->loadImage(_maskImageName, _maskImage);
|
||||
|
||||
_zBuffer = new byte[_drawSurface.w * _drawSurface.h];
|
||||
memset(_zBuffer, -1, _drawSurface.w * _drawSurface.h);
|
||||
|
||||
_tiles.resize(_tileSrcs.size() + 1);
|
||||
|
||||
// First, add the mask as its own tile for easier handling
|
||||
Tile *curTile = &_tiles[0];
|
||||
curTile->_srcImage.create(_maskImage, _maskImage.getBounds());
|
||||
curTile->_drawSurface.copyFrom(_tiles[0]._srcImage);
|
||||
curTile->_id = 0;
|
||||
curTile->drawMask();
|
||||
curTile->moveTo(_maskImage.getBounds());
|
||||
curTile->setTransparent(true);
|
||||
curTile->setVisible(true);
|
||||
drawToBuffer(*curTile);
|
||||
curTile->setZ(_z + 1);
|
||||
|
||||
// Then, add the actual tiles
|
||||
for (uint i = 0; i < _tileSrcs.size(); ++i) {
|
||||
curTile = &_tiles[i + 1];
|
||||
curTile->_srcImage.create(_tileImage, _tileSrcs[i]);
|
||||
curTile->_drawSurface.copyFrom(curTile->_srcImage);
|
||||
curTile->_id = i + 1;
|
||||
curTile->moveTo(_tileDests[i]);
|
||||
curTile->setTransparent(true);
|
||||
curTile->setVisible(true);
|
||||
curTile->setZ(_z + curTile->_id + 1);
|
||||
curTile->drawMask();
|
||||
drawToBuffer(*curTile);
|
||||
|
||||
// Draw the highlighted tile
|
||||
curTile->_highlightedSrcImage.copyFrom(curTile->_srcImage);
|
||||
|
||||
Graphics::PixelFormat format = curTile->_highlightedSrcImage.format;
|
||||
for (int y = 0; y < curTile->_highlightedSrcImage.h; ++y) {
|
||||
uint16 *p = (uint16 *)curTile->_highlightedSrcImage.getBasePtr(0, y);
|
||||
for (int x = 0; x < curTile->_highlightedSrcImage.w; ++x) {
|
||||
if (*p != g_nancy->_graphics->getTransColor()) {
|
||||
// I'm not sure *3/2 is the exact formula but it's close enough
|
||||
byte r, g, b;
|
||||
format.colorToRGB(*p, r, g, b);
|
||||
r = (byte)((((uint16)r) * 3) >> 1);
|
||||
g = (byte)((((uint16)g) * 3) >> 1);
|
||||
b = (byte)((((uint16)b) * 3) >> 1);
|
||||
*p = (uint16)format.RGBToColor((byte)r, (byte)g, (byte)b);
|
||||
}
|
||||
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void TangramPuzzle::registerGraphics() {
|
||||
for (Tile &tile : _tiles) {
|
||||
tile.registerGraphics();
|
||||
}
|
||||
|
||||
RenderActionRecord::registerGraphics();
|
||||
}
|
||||
|
||||
void TangramPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _tileImageName);
|
||||
readFilename(stream, _maskImageName);
|
||||
|
||||
stream.skip(2); // Supposedly number of tiles, actually useless
|
||||
|
||||
for (uint i = 0; i < 15; ++i) {
|
||||
Common::Rect src, dest;
|
||||
readRect(stream, src);
|
||||
readRect(stream, dest);
|
||||
|
||||
if ((src.width() == 1 && src.height() == 1) || (dest.width() == 1 && dest.height() == 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_tileSrcs.push_back(src);
|
||||
_tileDests.push_back(dest);
|
||||
}
|
||||
|
||||
readRect(stream, _maskSolveBounds);
|
||||
|
||||
_pickUpSound.readNormal(stream);
|
||||
_putDownSound.readNormal(stream);
|
||||
_rotateSound.readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TangramPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_pickUpSound);
|
||||
g_nancy->_sound->loadSound(_putDownSound);
|
||||
g_nancy->_sound->loadSound(_rotateSound);
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_pickedUpTile == -1 && _shouldCheck) {
|
||||
for (int y = 0; y < _maskSolveBounds.height(); ++y) {
|
||||
byte *p = &_zBuffer[(y + _maskSolveBounds.top) * _drawSurface.w + _maskSolveBounds.left];
|
||||
for (int x = 0; x < _maskSolveBounds.width(); ++x) {
|
||||
if (*p == 0) {
|
||||
_shouldCheck = false;
|
||||
return;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solved = true;
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
if (_solved) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
break;
|
||||
}
|
||||
|
||||
_solveScene.execute();
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
g_nancy->_sound->stopSound(_pickUpSound);
|
||||
g_nancy->_sound->stopSound(_putDownSound);
|
||||
g_nancy->_sound->stopSound(_rotateSound);
|
||||
|
||||
finishExecution();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Rect viewport = NancySceneState.getViewport().getScreenPosition();
|
||||
|
||||
if (!viewport.contains(input.mousePos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Point mousePos = input.mousePos;
|
||||
mousePos.x -= viewport.left;
|
||||
mousePos.y -= viewport.top;
|
||||
|
||||
viewport.moveTo(Common::Point(0, 0));
|
||||
|
||||
if (_pickedUpTile == -1) {
|
||||
// Not holding a tile, check what's under the cursor
|
||||
byte idUnderMouse = _zBuffer[mousePos.y * _drawSurface.w + mousePos.x];
|
||||
|
||||
if (idUnderMouse != 0 && idUnderMouse != (byte)-1) {
|
||||
// A tile is under the cursor
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
pickUpTile(idUnderMouse);
|
||||
g_nancy->_sound->playSound(_pickUpSound);
|
||||
} else if (input.input & NancyInput::kRightMouseButtonUp) {
|
||||
rotateTile(idUnderMouse);
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// No tile under cursor, check exit hotspot
|
||||
if (_exitHotspot.contains(mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Currently holding a tile
|
||||
Tile &tileHolding = _tiles[_pickedUpTile];
|
||||
|
||||
// Check if we need to place it back down
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
putDownTile(_pickedUpTile);
|
||||
g_nancy->_sound->playSound(_putDownSound);
|
||||
return;
|
||||
}
|
||||
|
||||
tileHolding.handleInput(input);
|
||||
bool rotated = false;
|
||||
|
||||
// Check if we need to rotate it
|
||||
if (input.input & NancyInput::kRightMouseButtonUp) {
|
||||
rotateTile(_pickedUpTile);
|
||||
g_nancy->_sound->playSound(_rotateSound);
|
||||
rotated = true;
|
||||
}
|
||||
|
||||
if (!rotated) {
|
||||
// Check if we need to highlight, but only if we haven't rotated,
|
||||
// since rotateTile() already checks as well
|
||||
if (checkBuffer(tileHolding) != tileHolding._isHighlighted) {
|
||||
tileHolding.setHighlighted(!tileHolding._isHighlighted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::drawToBuffer(const Tile &tile, Common::Rect subRect) {
|
||||
if (subRect.isEmpty()) {
|
||||
subRect = tile._screenPosition;
|
||||
}
|
||||
|
||||
uint16 xDiff = subRect.left - tile._screenPosition.left;
|
||||
uint16 yDiff = subRect.top - tile._screenPosition.top;
|
||||
|
||||
for (int y = 0; y < subRect.height(); ++y) {
|
||||
byte *src = &tile._mask[(y + yDiff) * tile._drawSurface.w + xDiff];
|
||||
byte *dest = &_zBuffer[(subRect.top + y) * _drawSurface.w + subRect.left];
|
||||
for (int x = 0; x < subRect.width(); ++x) {
|
||||
if (*src != (byte)-1) {
|
||||
*dest = *src;
|
||||
}
|
||||
|
||||
++src;
|
||||
++dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::pickUpTile(uint id) {
|
||||
assert(id < _tiles.size() && id != 0);
|
||||
|
||||
Tile &tileToPickUp = _tiles[id];
|
||||
|
||||
moveToTop(id);
|
||||
_pickedUpTile = id;
|
||||
redrawBuffer(tileToPickUp._screenPosition);
|
||||
tileToPickUp.pickUp();
|
||||
|
||||
// Make sure we don't have a frame with the correct zOrder, but wrong position
|
||||
NancyInput input = g_nancy->_input->getInput();
|
||||
input.input = 0;
|
||||
handleInput(input);
|
||||
}
|
||||
|
||||
void TangramPuzzle::putDownTile(uint id) {
|
||||
Tile &tile = _tiles[id];
|
||||
_pickedUpTile = -1;
|
||||
|
||||
drawToBuffer(tile);
|
||||
tile.putDown();
|
||||
|
||||
if (tile._isHighlighted) {
|
||||
tile.setHighlighted(false);
|
||||
}
|
||||
|
||||
_shouldCheck = true;
|
||||
}
|
||||
|
||||
void TangramPuzzle::rotateTile(uint id) {
|
||||
assert(id < _tiles.size() && id != 0);
|
||||
|
||||
Tile &tileToRotate = _tiles[id];
|
||||
|
||||
if (tileToRotate._rotation == 3) {
|
||||
tileToRotate._rotation = 0;
|
||||
} else {
|
||||
++tileToRotate._rotation;
|
||||
}
|
||||
|
||||
moveToTop(id);
|
||||
|
||||
Common::Rect oldPos = tileToRotate._screenPosition;
|
||||
|
||||
if (_pickedUpTile != -1 && checkBuffer(tileToRotate)) {
|
||||
tileToRotate.setHighlighted(true);
|
||||
} else {
|
||||
tileToRotate.setHighlighted(false);
|
||||
}
|
||||
|
||||
Common::Rect newPos = tileToRotate._drawSurface.getBounds();
|
||||
newPos.moveTo(oldPos.left + oldPos.width() / 2 - newPos.width() / 2, oldPos.top + oldPos.height() / 2 - newPos.height() / 2);
|
||||
tileToRotate.moveTo(newPos);
|
||||
_needsRedraw = true;
|
||||
|
||||
tileToRotate.drawMask();
|
||||
tileToRotate._needsRedraw = true;
|
||||
|
||||
if (_pickedUpTile == -1) {
|
||||
redrawBuffer(oldPos);
|
||||
drawToBuffer(tileToRotate);
|
||||
_shouldCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::moveToTop(uint id) {
|
||||
for (uint i = 1; i < _tiles.size(); ++i) {
|
||||
Tile &tile = _tiles[i];
|
||||
if (tile._z > _tiles[id]._z) {
|
||||
tile.setZ(tile._z - 1);
|
||||
tile.registerGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
_tiles[id].setZ(_z + _tiles.size());
|
||||
_tiles[id].registerGraphics();
|
||||
}
|
||||
|
||||
void TangramPuzzle::redrawBuffer(const Common::Rect &rect) {
|
||||
// Redraw the zBuffer for all intersecting pixels, except for the topmost tile
|
||||
for (int y = 0; y < rect.height(); ++y) {
|
||||
byte *dest = &_zBuffer[(y + rect.top) * _drawSurface.w + rect.left];
|
||||
memset(dest, -1, rect.width());
|
||||
}
|
||||
|
||||
for (uint z = _z + 1; z < _z + _tiles.size(); ++z) {
|
||||
for (uint i = 0; i < _tiles.size() - 1; ++i) {
|
||||
Tile &tile = _tiles[i];
|
||||
if (tile._z == z) {
|
||||
if (tile._screenPosition.intersects(rect)) {
|
||||
drawToBuffer(tile, tile._screenPosition.findIntersectingRect(rect));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TangramPuzzle::checkBuffer(const Tile &tile) const {
|
||||
// Check if the provided tile has any pixel overlapping with a non-zero in the zBuffer
|
||||
// In other words, this checks if we're placing on a valid empty spot
|
||||
for (int y = 0; y < tile._drawSurface.h; ++y) {
|
||||
const byte *tilePtr = &tile._mask[y * tile._drawSurface.w];
|
||||
const byte *bufPtr = &_zBuffer[(y + tile._screenPosition.top) * _drawSurface.w + tile._screenPosition.left];
|
||||
for (int x = 0; x < tile._drawSurface.w; ++x) {
|
||||
if (*tilePtr != (byte)-1 && *bufPtr != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++tilePtr;
|
||||
++bufPtr;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TangramPuzzle::Tile::Tile() : _mask(nullptr), _id(0), _rotation(0), _isHighlighted(false) {}
|
||||
|
||||
TangramPuzzle::Tile::~Tile() {
|
||||
delete[] _mask;
|
||||
}
|
||||
|
||||
void TangramPuzzle::Tile::drawMask() {
|
||||
if (!_mask) {
|
||||
_mask = new byte[_drawSurface.w * _drawSurface.h];
|
||||
}
|
||||
|
||||
uint16 transColor = g_nancy->_graphics->getTransColor();
|
||||
for (int y = 0; y < _drawSurface.h; ++y) {
|
||||
uint16 *src = (uint16 *)_drawSurface.getBasePtr(0, y);
|
||||
for (int x = 0; x < _drawSurface.w; ++x) {
|
||||
if (*src == transColor) {
|
||||
_mask[y * _drawSurface.w + x] = (byte)-1;
|
||||
} else {
|
||||
_mask[y * _drawSurface.w + x] = _id;
|
||||
}
|
||||
++src;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TangramPuzzle::Tile::setHighlighted(bool highlighted) {
|
||||
_isHighlighted = highlighted;
|
||||
GraphicsManager::rotateBlit(_isHighlighted ? _highlightedSrcImage : _srcImage,
|
||||
_drawSurface,
|
||||
_rotation);
|
||||
setTransparent(true);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
115
engines/nancy/action/puzzle/tangrampuzzle.h
Normal file
115
engines/nancy/action/puzzle/tangrampuzzle.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TANGRAMPUZZLE_H
|
||||
#define NANCY_ACTION_TANGRAMPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Handles a specific type of puzzle where clicking an object rotates it,
|
||||
// as well as several other objects linked to it. Examples are the sun/moon
|
||||
// and staircase spindle puzzles in nancy3
|
||||
class TangramPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
TangramPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TangramPuzzle();
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TangramPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
class Tile : public Misc::MouseFollowObject {
|
||||
friend class TangramPuzzle;
|
||||
public:
|
||||
Tile();
|
||||
virtual ~Tile();
|
||||
|
||||
void drawMask();
|
||||
void setHighlighted(bool highlighted);
|
||||
|
||||
Graphics::ManagedSurface _srcImage;
|
||||
Graphics::ManagedSurface _highlightedSrcImage;
|
||||
byte *_mask;
|
||||
byte _id;
|
||||
byte _rotation;
|
||||
bool _isHighlighted;
|
||||
|
||||
protected:
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
void drawToBuffer(const Tile &tile, Common::Rect subRect = Common::Rect());
|
||||
|
||||
void pickUpTile(uint id);
|
||||
void putDownTile(uint id);
|
||||
void rotateTile(uint id);
|
||||
|
||||
void moveToTop(uint id);
|
||||
void redrawBuffer(const Common::Rect &rect);
|
||||
|
||||
bool checkBuffer(const Tile &tile) const;
|
||||
|
||||
Common::Path _tileImageName;
|
||||
Common::Path _maskImageName;
|
||||
|
||||
Common::Array<Common::Rect> _tileSrcs;
|
||||
Common::Array<Common::Rect> _tileDests;
|
||||
|
||||
Common::Rect _maskSolveBounds;
|
||||
|
||||
SoundDescription _pickUpSound;
|
||||
SoundDescription _putDownSound;
|
||||
SoundDescription _rotateSound;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _tileImage;
|
||||
Graphics::ManagedSurface _maskImage;
|
||||
byte *_zBuffer = nullptr;
|
||||
|
||||
Common::Array<Tile> _tiles;
|
||||
|
||||
int16 _pickedUpTile = -1;
|
||||
bool _shouldCheck = false;
|
||||
bool _solved = false;
|
||||
|
||||
uint _pixelAdjustment = 5;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TANGRAMPUZZLE_H
|
||||
578
engines/nancy/action/puzzle/telephone.cpp
Normal file
578
engines/nancy/action/puzzle/telephone.cpp
Normal file
@@ -0,0 +1,578 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/font.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/telephone.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void Telephone::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
g_nancy->_resource->loadImage(_displayAnimName, _animImage);
|
||||
|
||||
if (_isNewPhone) {
|
||||
_font = g_nancy->_graphics->getFont(_displayFont);
|
||||
}
|
||||
|
||||
// Set the phone tutorial flag to false for Nancy9, so that
|
||||
// the actual phone interface is available after the tutorial.
|
||||
// TODO: Is this the right place to set this flag?
|
||||
if (g_nancy->getGameType() == kGameTypeNancy9) {
|
||||
NancySceneState.setEventFlag(592, g_nancy->_false);
|
||||
}
|
||||
}
|
||||
|
||||
void Telephone::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint16 numButtons = 12;
|
||||
uint16 maxNumButtons = _isNewPhone ? 20 : 12;
|
||||
|
||||
if (_isNewPhone) {
|
||||
_hasDisplay = stream.readByte();
|
||||
_displayFont = stream.readUint16LE();
|
||||
readFilename(stream, _displayAnimName);
|
||||
_displayAnimFrameTime = stream.readUint32LE();
|
||||
uint16 numFrames = stream.readUint16LE();
|
||||
readRectArray(stream, _displaySrcs, numFrames, 10);
|
||||
readRect(stream, _displayDest);
|
||||
_dialAutomatically = stream.readByte();
|
||||
|
||||
numButtons = stream.readUint16LE();
|
||||
}
|
||||
|
||||
readRectArray(stream, _srcRects, numButtons, maxNumButtons);
|
||||
readRectArray(stream, _destRects, numButtons, maxNumButtons);
|
||||
|
||||
if (_isNewPhone) {
|
||||
readRect(stream, _dirHighlightSrc);
|
||||
readRect(stream, _dialHighlightSrc);
|
||||
|
||||
_upDirButtonID = stream.readUint16LE();
|
||||
_downDirButtonID = stream.readUint16LE();
|
||||
_dialButtonID = stream.readUint16LE();
|
||||
_dirButtonID = stream.readUint16LE();
|
||||
|
||||
readRect(stream, _displayDialingSrc);
|
||||
}
|
||||
|
||||
if (!_isNewPhone) {
|
||||
_genericDialogueSound.readNormal(stream);
|
||||
_genericButtonSound.readNormal(stream);
|
||||
_ringSound.readNormal(stream);
|
||||
_dialToneSound.readNormal(stream);
|
||||
_dialAgainSound.readNormal(stream);
|
||||
_hangUpSound.readNormal(stream);
|
||||
} else {
|
||||
_ringSound.readNormal(stream);
|
||||
_dialToneSound.readNormal(stream);
|
||||
_preCallSound.readNormal(stream);
|
||||
_hangUpSound.readNormal(stream);
|
||||
_genericButtonSound.readNormal(stream);
|
||||
}
|
||||
|
||||
readFilenameArray(stream, _buttonSoundNames, numButtons);
|
||||
stream.skip(33 * (maxNumButtons - numButtons));
|
||||
|
||||
char textBuf[200];
|
||||
if (!_isNewPhone) {
|
||||
stream.read(textBuf, 200);
|
||||
textBuf[199] = '\0';
|
||||
_addressBookString = textBuf;
|
||||
} else {
|
||||
_dialAgainSound.readNormal(stream);
|
||||
}
|
||||
|
||||
stream.read(textBuf, 200);
|
||||
textBuf[199] = '\0';
|
||||
_dialAgainString = textBuf;
|
||||
|
||||
_reloadScene.readData(stream);
|
||||
stream.skip(1);
|
||||
_exitScene.readData(stream);
|
||||
stream.skip(1);
|
||||
readRect(stream, _exitHotspot);
|
||||
|
||||
uint numCalls = stream.readUint16LE();
|
||||
|
||||
_calls.resize(numCalls);
|
||||
for (uint i = 0; i < numCalls; ++i) {
|
||||
PhoneCall &call = _calls[i];
|
||||
|
||||
if (_isNewPhone) {
|
||||
call.directoryDisplayCondition = stream.readSint16LE();
|
||||
}
|
||||
|
||||
call.phoneNumber.resize(11);
|
||||
for (uint j = 0; j < 11; ++j) {
|
||||
call.phoneNumber[j] = stream.readByte();
|
||||
}
|
||||
|
||||
if (!_isNewPhone) {
|
||||
readFilename(stream, call.soundName);
|
||||
stream.read(textBuf, 200);
|
||||
textBuf[199] = '\0';
|
||||
call.text = textBuf;
|
||||
} else {
|
||||
readRect(stream, call.displaySrc);
|
||||
}
|
||||
|
||||
call.sceneChange.readData(stream);
|
||||
stream.skip(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Telephone::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_sound->loadSound(_dialToneSound);
|
||||
g_nancy->_sound->playSound(_dialToneSound);
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_addressBookString);
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_callState) {
|
||||
case kWaiting:
|
||||
if (_isNewPhone && !_animIsStopped) {
|
||||
if (g_nancy->getTotalPlayTime() > _displayAnimEnd) {
|
||||
if (_displayAnimEnd == 0) {
|
||||
_displayAnimEnd = g_nancy->getTotalPlayTime() + _displayAnimFrameTime;
|
||||
} else {
|
||||
_displayAnimEnd += _displayAnimFrameTime;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_animImage, _displaySrcs[_displayAnimFrame], _displayDest);
|
||||
_needsRedraw = true;
|
||||
++_displayAnimFrame;
|
||||
|
||||
if (_displayAnimFrame >= _displaySrcs.size()) {
|
||||
_displayAnimFrame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_checkNumbers) {
|
||||
// Pressed a new button, check all numbers for match
|
||||
// We do this before going to the ringing state to support nancy4's voice mail system,
|
||||
// where call numbers can be 1 digit long
|
||||
for (uint i = 0; i < _calls.size(); ++i) {
|
||||
auto &call = _calls[i];
|
||||
bool invalid = false;
|
||||
|
||||
for (uint j = 0; j < _calledNumber.size(); ++j) {
|
||||
if (_calledNumber[j] != call.phoneNumber[j]) {
|
||||
// Invalid number, move onto next
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We do not want to check for a terminator if the dialed number is of
|
||||
// appropriate size (7 digits, or 11 when the number starts with '1')
|
||||
bool checkNextDigit = true;
|
||||
if (_calledNumber.size() >= 11 || (_calledNumber.size() >= 7 && (_calledNumber[0] != 1))) {
|
||||
checkNextDigit = false;
|
||||
}
|
||||
|
||||
if (!invalid && checkNextDigit) {
|
||||
// Check if the next digit in the phone number is '10' (star). Presumably, that will never
|
||||
// be contained in a valid phone number
|
||||
if (_calls[i].phoneNumber[_calledNumber.size()] != 10) {
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!invalid) {
|
||||
_selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldRing = false;
|
||||
|
||||
if (_selected == -1) {
|
||||
// Did not find a suitable match, check if the dialed number is above allowed size
|
||||
if (_calledNumber.size() >= 11 || (_calledNumber.size() >= 7 && (_calledNumber[0] != 1))) {
|
||||
shouldRing = true;
|
||||
}
|
||||
} else {
|
||||
shouldRing = true;
|
||||
}
|
||||
|
||||
if (shouldRing) {
|
||||
if (_ringSound.name != "NO SOUND") {
|
||||
if (_hasDisplay) {
|
||||
_drawSurface.blitFrom(_image, _displayDialingSrc, _displayDest);
|
||||
_needsRedraw = true;
|
||||
} else {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(g_nancy->getStaticData().ringingText);
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_ringSound);
|
||||
g_nancy->_sound->playSound(_ringSound);
|
||||
}
|
||||
|
||||
_callState = kRinging;
|
||||
}
|
||||
|
||||
_checkNumbers = false;
|
||||
}
|
||||
|
||||
break;
|
||||
case kButtonPress:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_genericButtonSound)) {
|
||||
g_nancy->_sound->stopSound(_genericButtonSound);
|
||||
_drawSurface.fillRect(_destRects[_buttonLastPushed], g_nancy->_graphics->getTransColor());
|
||||
_needsRedraw = true;
|
||||
|
||||
if (_isShowingDirectory) {
|
||||
_drawSurface.blitFrom(_image, _dirHighlightSrc, _destRects[_dirButtonID]);
|
||||
_drawSurface.blitFrom(_image, _calls[_displayedDirectory].displaySrc, _displayDest);
|
||||
} else if (_dirButtonID != -1) {
|
||||
_drawSurface.fillRect(_destRects[_dirButtonID], _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_buttonLastPushed = -1;
|
||||
_callState = kWaiting;
|
||||
}
|
||||
|
||||
break;
|
||||
case kRinging:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_ringSound)) {
|
||||
g_nancy->_sound->stopSound(_ringSound);
|
||||
|
||||
if (_selected != -1) {
|
||||
// Called a valid number
|
||||
|
||||
if (_preCallSound.name == "NO SOUND") {
|
||||
// Old phone, go directly to call
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_calls[_selected].text);
|
||||
|
||||
_genericDialogueSound.name = _calls[_selected].soundName;
|
||||
g_nancy->_sound->loadSound(_genericDialogueSound);
|
||||
g_nancy->_sound->playSound(_genericDialogueSound);
|
||||
_callState = kCall;
|
||||
} else {
|
||||
// New phone, play a short sound of phone being picked up
|
||||
g_nancy->_sound->loadSound(_preCallSound);
|
||||
g_nancy->_sound->playSound(_preCallSound);
|
||||
_callState = kPreCall;
|
||||
}
|
||||
} else {
|
||||
// Called an invalid number
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_dialAgainString);
|
||||
|
||||
if (_hasDisplay) {
|
||||
_drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
if (_dialButtonID != -1) {
|
||||
_drawSurface.fillRect(_destRects[_dialButtonID], _drawSurface.getTransparentColor());
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
_calledNumber.clear();
|
||||
|
||||
g_nancy->_sound->loadSound(_dialAgainSound);
|
||||
g_nancy->_sound->playSound(_dialAgainSound);
|
||||
_callState = kBadNumber;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case kPreCall:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_preCallSound)) {
|
||||
g_nancy->_sound->stopSound(_preCallSound);
|
||||
|
||||
if (!_calls[_selected].text.empty()) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(_calls[_selected].text);
|
||||
}
|
||||
|
||||
_genericDialogueSound.name = _calls[_selected].soundName;
|
||||
g_nancy->_sound->loadSound(_genericDialogueSound);
|
||||
g_nancy->_sound->playSound(_genericDialogueSound);
|
||||
_callState = kCall;
|
||||
}
|
||||
|
||||
break;
|
||||
case kBadNumber:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_dialAgainSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kCall:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_genericDialogueSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
case kHangUp:
|
||||
if (!g_nancy->_sound->isSoundPlaying(_hangUpSound)) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
switch (_callState) {
|
||||
case kBadNumber:
|
||||
_reloadScene.execute();
|
||||
_calledNumber.clear();
|
||||
_state = kRun;
|
||||
_callState = kWaiting;
|
||||
|
||||
break;
|
||||
case kCall: {
|
||||
PhoneCall &call = _calls[_selected];
|
||||
|
||||
// Make sure we don't get stuck here. Happens in nancy3 when calling George's number
|
||||
// Check ignored in nancy1 since the HintSystem AR is in the same scene as the Telephone
|
||||
if (call.sceneChange._sceneChange.sceneID == kNoScene && g_nancy->getGameType() != kGameTypeNancy1) {
|
||||
call.sceneChange._sceneChange = NancySceneState.getSceneInfo();
|
||||
}
|
||||
|
||||
call.sceneChange.execute();
|
||||
|
||||
break;
|
||||
}
|
||||
case kHangUp:
|
||||
_exitScene.execute();
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_hangUpSound);
|
||||
g_nancy->_sound->stopSound(_genericDialogueSound);
|
||||
g_nancy->_sound->stopSound(_genericButtonSound);
|
||||
g_nancy->_sound->stopSound(_dialAgainSound);
|
||||
g_nancy->_sound->stopSound(_ringSound);
|
||||
g_nancy->_sound->stopSound(_dialToneSound);
|
||||
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void Telephone::handleInput(NancyInput &input) {
|
||||
int buttonNr = -1;
|
||||
// Cursor gets changed regardless of state
|
||||
for (int i = 0; i < (int)_destRects.size(); ++i) {
|
||||
// Dial button is an exception
|
||||
if (i == _dialButtonID && !_calledNumber.size() && !_isShowingDirectory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_destRects[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
buttonNr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_callState != kWaiting && _callState != kRinging) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->loadSound(_hangUpSound);
|
||||
g_nancy->_sound->playSound(_hangUpSound);
|
||||
|
||||
_callState = kHangUp;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_callState != kWaiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonNr != -1) {
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
if (g_nancy->_sound->isSoundPlaying(_dialToneSound)) {
|
||||
g_nancy->_sound->stopSound(_dialToneSound);
|
||||
}
|
||||
|
||||
// Handle non-digit numbers
|
||||
bool directorySwitch = false;
|
||||
bool changeDirectoryEntry = false;
|
||||
int dirEntryDelta = 1;
|
||||
if (_dialButtonID != -1 && buttonNr == _dialButtonID) {
|
||||
if (_isShowingDirectory) {
|
||||
_calledNumber = _calls[_displayedDirectory].phoneNumber;
|
||||
while (_calledNumber.back() == 10) {
|
||||
_calledNumber.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
_checkNumbers = true;
|
||||
|
||||
// Dial button doesn't make sound, and doesn't get pressed down
|
||||
_drawSurface.blitFrom(_image, _dialHighlightSrc, _destRects[_dialButtonID]);
|
||||
|
||||
if (_dirButtonID != -1) {
|
||||
_drawSurface.fillRect(_destRects[_dirButtonID], _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_animIsStopped = true;
|
||||
return;
|
||||
} else if (_upDirButtonID != -1 && buttonNr == _upDirButtonID) {
|
||||
if (!_isShowingDirectory) {
|
||||
directorySwitch = true;
|
||||
} else {
|
||||
++_displayedDirectory;
|
||||
changeDirectoryEntry = true;
|
||||
}
|
||||
_animIsStopped = true;
|
||||
} else if (_downDirButtonID != -1 && buttonNr == _downDirButtonID) {
|
||||
if (!_isShowingDirectory) {
|
||||
directorySwitch = true;
|
||||
} else {
|
||||
--_displayedDirectory;
|
||||
dirEntryDelta = -1;
|
||||
changeDirectoryEntry = true;
|
||||
}
|
||||
_animIsStopped = true;
|
||||
} else if (_dirButtonID != -1 && buttonNr == _dirButtonID) {
|
||||
if (!_isShowingDirectory) {
|
||||
directorySwitch = true;
|
||||
}
|
||||
_animIsStopped = true;
|
||||
} else {
|
||||
if (_isShowingDirectory || !_calledNumber.size()) {
|
||||
_isShowingDirectory = false;
|
||||
_displayedDirectory = 0;
|
||||
_drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
_calledNumber.push_back(buttonNr);
|
||||
_checkNumbers = _dialAutomatically;
|
||||
_animIsStopped = true;
|
||||
|
||||
if (_calledNumber.size() > 11) {
|
||||
_calledNumber.clear();
|
||||
|
||||
if (_hasDisplay) {
|
||||
_drawSurface.fillRect(_displayDest, _drawSurface.getTransparentColor());
|
||||
} else if (_isNewPhone) {
|
||||
NancySceneState.getTextbox().clear();
|
||||
}
|
||||
|
||||
_checkNumbers = false;
|
||||
}
|
||||
|
||||
if (_isNewPhone && _calledNumber.size()) {
|
||||
Common::String numberString;
|
||||
for (uint j = 0; j < _calledNumber.size(); ++j) {
|
||||
numberString += '0' + _calledNumber[j];
|
||||
}
|
||||
|
||||
if (_hasDisplay) {
|
||||
_font->drawString(&_drawSurface, numberString, _displayDest.left + 19, _displayDest.top + 21 - _font->getFontHeight(),
|
||||
_displayDest.width() - 20, 0);
|
||||
} else {
|
||||
NancySceneState.getTextbox().clear();
|
||||
NancySceneState.getTextbox().addTextLine(numberString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (directorySwitch) {
|
||||
// Handle switch to directory mode
|
||||
_isShowingDirectory = true;
|
||||
changeDirectoryEntry = true;
|
||||
_calledNumber.clear();
|
||||
}
|
||||
|
||||
if (changeDirectoryEntry) {
|
||||
int start = _displayedDirectory;
|
||||
|
||||
do {
|
||||
if (_displayedDirectory >= (int)_calls.size()) {
|
||||
_displayedDirectory = 0;
|
||||
} else if (_displayedDirectory < 0) {
|
||||
_displayedDirectory = _calls.size() - 1;
|
||||
}
|
||||
|
||||
if (_calls[_displayedDirectory].directoryDisplayCondition == kEvNoEvent) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (NancySceneState.getEventFlag(_calls[_displayedDirectory].directoryDisplayCondition, g_nancy->_true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
_displayedDirectory += dirEntryDelta;
|
||||
} while (_displayedDirectory != start);
|
||||
}
|
||||
|
||||
_genericButtonSound.name = _buttonSoundNames[buttonNr];
|
||||
g_nancy->_sound->loadSound(_genericButtonSound);
|
||||
g_nancy->_sound->playSound(_genericButtonSound);
|
||||
|
||||
_drawSurface.blitFrom(_image, _srcRects[buttonNr], _destRects[buttonNr]);
|
||||
_needsRedraw = true;
|
||||
|
||||
_displayAnimEnd = 0;
|
||||
_displayAnimFrame = 0;
|
||||
|
||||
_buttonLastPushed = buttonNr;
|
||||
_callState = kButtonPress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
130
engines/nancy/action/puzzle/telephone.h
Normal file
130
engines/nancy/action/puzzle/telephone.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TELEPHONE_H
|
||||
#define NANCY_ACTION_TELEPHONE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
class Font;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class Telephone : public RenderActionRecord {
|
||||
public:
|
||||
struct PhoneCall {
|
||||
Common::Array<byte> phoneNumber;
|
||||
Common::String soundName;
|
||||
Common::String text;
|
||||
SceneChangeWithFlag sceneChange;
|
||||
|
||||
// NewPhone members
|
||||
int16 directoryDisplayCondition = -1;
|
||||
Common::Rect displaySrc;
|
||||
};
|
||||
|
||||
enum CallState { kWaiting, kButtonPress, kRinging, kBadNumber, kPreCall, kCall, kHangUp };
|
||||
|
||||
Telephone(bool isNewPhone) :
|
||||
RenderActionRecord(7),
|
||||
_callState(kWaiting),
|
||||
_buttonLastPushed(-1),
|
||||
_selected(-1),
|
||||
_checkNumbers(false),
|
||||
_font(nullptr),
|
||||
_animIsStopped(false),
|
||||
_isNewPhone(isNewPhone) {}
|
||||
virtual ~Telephone() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return _isNewPhone ? "NewPhone" : "Telephone"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<Common::Rect> _srcRects;
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
SoundDescription _genericDialogueSound;
|
||||
SoundDescription _genericButtonSound;
|
||||
SoundDescription _ringSound;
|
||||
SoundDescription _dialToneSound;
|
||||
SoundDescription _dialAgainSound;
|
||||
SoundDescription _hangUpSound;
|
||||
Common::Array<Common::String> _buttonSoundNames;
|
||||
Common::String _addressBookString;
|
||||
Common::String _dialAgainString;
|
||||
SceneChangeWithFlag _reloadScene;
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
Common::Array<PhoneCall> _calls;
|
||||
|
||||
// NewPhone properties
|
||||
bool _hasDisplay = false;
|
||||
uint16 _displayFont = 0;
|
||||
Common::Path _displayAnimName;
|
||||
uint32 _displayAnimFrameTime = 0;
|
||||
Common::Array<Common::Rect> _displaySrcs;
|
||||
Common::Rect _displayDest;
|
||||
|
||||
bool _dialAutomatically = true;
|
||||
|
||||
Common::Rect _dirHighlightSrc;
|
||||
Common::Rect _dialHighlightSrc;
|
||||
|
||||
int16 _upDirButtonID = -1;
|
||||
int16 _downDirButtonID = -1;
|
||||
int16 _dialButtonID = -1;
|
||||
int16 _dirButtonID = -1;
|
||||
|
||||
Common::Rect _displayDialingSrc;
|
||||
|
||||
SoundDescription _preCallSound;
|
||||
|
||||
Common::Array<byte> _calledNumber;
|
||||
Graphics::ManagedSurface _image;
|
||||
Graphics::ManagedSurface _animImage;
|
||||
CallState _callState;
|
||||
int _buttonLastPushed;
|
||||
int _selected;
|
||||
bool _checkNumbers;
|
||||
bool _animIsStopped;
|
||||
|
||||
uint32 _displayAnimEnd = 0;
|
||||
uint16 _displayAnimFrame = 0;
|
||||
int16 _displayedDirectory = 0;
|
||||
bool _isShowingDirectory = false;
|
||||
|
||||
const Font *_font;
|
||||
|
||||
bool _isNewPhone;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TELEPHONE_H
|
||||
305
engines/nancy/action/puzzle/towerpuzzle.cpp
Normal file
305
engines/nancy/action/puzzle/towerpuzzle.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
/* 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/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/towerpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void TowerPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
_image.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
}
|
||||
|
||||
void TowerPuzzle::registerGraphics() {
|
||||
_heldRing.registerGraphics();
|
||||
RenderObject::registerGraphics();
|
||||
}
|
||||
|
||||
void TowerPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
_puzzleState = (TowerPuzzleData *)NancySceneState.getPuzzleData(TowerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
_numRingsByDifficulty.resize(3);
|
||||
for (uint i = 0; i < 3; ++i) {
|
||||
_numRingsByDifficulty[i] = stream.readUint16LE();
|
||||
}
|
||||
|
||||
stream.skip(2);
|
||||
|
||||
readRectArray(stream, _droppedRingSrcs, 6);
|
||||
readRectArray(stream, _heldRingSrcs, 6);
|
||||
|
||||
readRectArray(stream, _hotspots, 3);
|
||||
|
||||
_destRects.resize(6);
|
||||
for (uint ringID = 0; ringID < 6; ++ringID) {
|
||||
_destRects[ringID].resize(3);
|
||||
for (uint poleID = 0; poleID < 3; ++poleID) {
|
||||
// Biggest ring can only be in bottom position,
|
||||
// so it only has one rect per pole; second-biggest can
|
||||
// be in bottom-most and one position above it, so it has
|
||||
// two rects per pole, etc. Skipped data is array of 0xFF.
|
||||
readRectArray(stream, _destRects[ringID][poleID], ringID + 1, 6);
|
||||
}
|
||||
}
|
||||
|
||||
_takeSound.readNormal(stream);
|
||||
_dropSound.readNormal(stream);
|
||||
|
||||
_solveExitScene._sceneChange.readData(stream);
|
||||
stream.skip(2);
|
||||
_solveSound.readNormal(stream);
|
||||
_solveExitScene._flag.label = stream.readSint16LE();
|
||||
_solveExitScene._flag.flag = stream.readByte();
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TowerPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
_puzzleState = (TowerPuzzleData *)NancySceneState.getPuzzleData(TowerPuzzleData::getTag());
|
||||
assert(_puzzleState);
|
||||
|
||||
init();
|
||||
registerGraphics();
|
||||
_numRings = _numRingsByDifficulty[NancySceneState.getDifficulty()];
|
||||
|
||||
if (!_puzzleState->playerHasTriedPuzzle) {
|
||||
_puzzleState->order.clear();
|
||||
_puzzleState->order.resize(3, Common::Array<int8>(6, -1));
|
||||
for (uint i = 0; i < _numRings; ++i) {
|
||||
_puzzleState->order[0][i] = i;
|
||||
}
|
||||
_puzzleState->playerHasTriedPuzzle = true;
|
||||
}
|
||||
|
||||
for (uint poleID = 0; poleID < 3; ++poleID) {
|
||||
for (uint pos = 0; pos < _numRings; ++pos) {
|
||||
if (_puzzleState->order[poleID][pos] == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
drawRing(poleID, pos, _puzzleState->order[poleID][pos]);
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
g_nancy->_sound->loadSound(_takeSound);
|
||||
g_nancy->_sound->loadSound(_dropSound);
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
switch (_solveState) {
|
||||
case kNotSolved :
|
||||
for (uint i = 0; i < _numRings; ++i) {
|
||||
// Win condition is valid for both middle and right pole
|
||||
if (_puzzleState->order[1][i] != (int8)i && _puzzleState->order[2][i] != (int8)i) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
_solveState = kWaitForSound;
|
||||
break;
|
||||
case kWaitForSound :
|
||||
if (!g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
switch (_solveState) {
|
||||
case kNotSolved:
|
||||
_exitScene.execute();
|
||||
break;
|
||||
case kWaitForSound:
|
||||
_solveExitScene.execute();
|
||||
_puzzleState->playerHasTriedPuzzle = false;
|
||||
_puzzleState->order.clear();
|
||||
_puzzleState->order.resize(3, Common::Array<int8>(6, -1));
|
||||
break;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_takeSound);
|
||||
g_nancy->_sound->stopSound(_dropSound);
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void TowerPuzzle::handleInput(NancyInput &input) {
|
||||
if (_state != kRun && _solveState != kNotSolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: this is a click-and-drag puzzle
|
||||
|
||||
// Check if mouse is above a pole hotspot
|
||||
// and change the cursor if needed
|
||||
int hoveredPoleID = -1;
|
||||
for (uint poleID = 0; poleID < 3; ++poleID) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[poleID]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
hoveredPoleID = poleID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_heldRingID == -1) {
|
||||
// Not holding a ring
|
||||
|
||||
// First, check the exit hotspot
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
// Player has clicked, exit
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we need to pick up a ring
|
||||
// Behavior is the same as original engine, where clicking outside a hotspot
|
||||
// and dragging the mouse inside while holding the click still triggers
|
||||
if (hoveredPoleID != -1 && (input.input & NancyInput::kLeftMouseButtonHeld)) {
|
||||
// Find the position of the topmost ring
|
||||
int ringPos;
|
||||
for (ringPos = 5; ringPos > -1; --ringPos) {
|
||||
if (_puzzleState->order[hoveredPoleID][ringPos] != -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ringPos == -1) {
|
||||
// Pole contains no rings, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
// Redraw so the ring isn't visible anymore
|
||||
drawRing(hoveredPoleID, ringPos, _puzzleState->order[hoveredPoleID][ringPos], true);
|
||||
|
||||
if (ringPos > 0) {
|
||||
drawRing(hoveredPoleID, ringPos - 1, _puzzleState->order[hoveredPoleID][ringPos - 1]);
|
||||
}
|
||||
|
||||
// Change the data
|
||||
SWAP<int8>(_heldRingID, _puzzleState->order[hoveredPoleID][ringPos]);
|
||||
_heldRingPoleID = hoveredPoleID;
|
||||
|
||||
// Show the held ring
|
||||
_heldRing._drawSurface.create(_image, _heldRingSrcs[_heldRingID]);
|
||||
_heldRing.setVisible(true);
|
||||
_heldRing.setTransparent(true);
|
||||
_heldRing.pickUp();
|
||||
|
||||
g_nancy->_sound->playSound(_takeSound);
|
||||
}
|
||||
}
|
||||
|
||||
if (_heldRingID != -1) {
|
||||
_heldRing.handleInput(input);
|
||||
|
||||
// Holding a ring, check if it has just been dropped
|
||||
if ((input.input & NancyInput::kLeftMouseButtonUp) || !(input.input & NancyInput::kLeftMouseButtonHeld)) {
|
||||
// Check if dropped over a pole hotspot
|
||||
// If not, return to old pole; if yes, move to new one
|
||||
uint returnToPole = hoveredPoleID == -1 ? _heldRingPoleID : hoveredPoleID;
|
||||
|
||||
// Find the new position of the ring
|
||||
uint newPos;
|
||||
for (newPos = 0; newPos < 6; ++newPos) {
|
||||
if (_puzzleState->order[returnToPole][newPos] == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the player can't place a larger ring on top of a smaller one
|
||||
if (newPos > 0 && _puzzleState->order[returnToPole][newPos - 1] > _heldRingID) {
|
||||
returnToPole = _heldRingPoleID;
|
||||
|
||||
for (newPos = 0; newPos < 6; ++newPos) {
|
||||
if (_puzzleState->order[returnToPole][newPos] == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the new ring in its place
|
||||
drawRing(returnToPole, newPos, _heldRingID);
|
||||
|
||||
// Change the data
|
||||
SWAP<int8>(_heldRingID, _puzzleState->order[returnToPole][newPos]);
|
||||
_heldRingPoleID = -1;
|
||||
|
||||
g_nancy->_sound->playSound(_dropSound);
|
||||
|
||||
// Hide the held ring
|
||||
_heldRing.setVisible(false);
|
||||
_heldRing.putDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TowerPuzzle::drawRing(uint poleID, uint position, uint ringID, bool clear) {
|
||||
_needsRedraw = true;
|
||||
|
||||
if (clear) {
|
||||
// Just clear the ring, leaving a hole in the surface
|
||||
// that needs to be filled by redrawing the ring below
|
||||
_drawSurface.fillRect(_destRects[ringID][poleID][position], _drawSurface.getTransparentColor());
|
||||
return;
|
||||
}
|
||||
|
||||
_drawSurface.blitFrom(_image, _droppedRingSrcs[ringID], _destRects[ringID][poleID][position]);
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
82
engines/nancy/action/puzzle/towerpuzzle.h
Normal file
82
engines/nancy/action/puzzle/towerpuzzle.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TOWERPUZZLE_H
|
||||
#define NANCY_ACTION_TOWERPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
#include "engines/nancy/misc/mousefollow.h"
|
||||
|
||||
namespace Nancy {
|
||||
|
||||
struct TowerPuzzleData;
|
||||
|
||||
namespace Action {
|
||||
|
||||
class TowerPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForSound };
|
||||
TowerPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TowerPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void registerGraphics() override;
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TowerPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawRing(uint poleID, uint position, uint ringID, bool clear = false);
|
||||
|
||||
Common::Path _imageName;
|
||||
Common::Array<uint16> _numRingsByDifficulty;
|
||||
|
||||
Common::Array<Common::Rect> _droppedRingSrcs;
|
||||
Common::Array<Common::Rect> _heldRingSrcs;
|
||||
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<Common::Array<Common::Array<Common::Rect>>> _destRects; // [ringID][poleID][position]
|
||||
|
||||
SoundDescription _takeSound;
|
||||
SoundDescription _dropSound;
|
||||
|
||||
SceneChangeWithFlag _solveExitScene;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Misc::MouseFollowObject _heldRing;
|
||||
int8 _heldRingID = -1;
|
||||
int8 _heldRingPoleID = -1;
|
||||
SolveState _solveState = kNotSolved;
|
||||
TowerPuzzleData *_puzzleState = nullptr;
|
||||
uint _numRings = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TOWERPUZZLE_H
|
||||
316
engines/nancy/action/puzzle/turningpuzzle.cpp
Normal file
316
engines/nancy/action/puzzle/turningpuzzle.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/* 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/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/turningpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void TurningPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void TurningPuzzle::updateGraphics() {
|
||||
if (_state == kBegin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_solveState == kWaitForAnimation) {
|
||||
if (g_nancy->getTotalPlayTime() > _nextTurnTime) {
|
||||
_nextTurnTime = g_nancy->getTotalPlayTime() + (_solveDelayBetweenTurns * 1000 / _currentOrder.size());
|
||||
|
||||
if ( (_turnFrameID == 0 && _solveAnimFace == 0) ||
|
||||
(_turnFrameID == 1 && _solveAnimFace > 0 && (int)_solveAnimFace < _numFaces - 1)) {
|
||||
g_nancy->_sound->playSound(_turnSound);
|
||||
}
|
||||
|
||||
if (_turnFrameID >= _numFramesPerTurn) {
|
||||
++_solveAnimFace;
|
||||
_turnFrameID = 0;
|
||||
_nextTurnTime += 1000 * _solveDelayBetweenTurns;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _currentOrder.size(); ++i) {
|
||||
uint faceID = _currentOrder[i] + _solveAnimFace;
|
||||
if (faceID >= _numFaces) {
|
||||
faceID -= _numFaces;
|
||||
}
|
||||
|
||||
drawObject(i, faceID, _turnFrameID);
|
||||
}
|
||||
|
||||
if ((int)_solveAnimFace >= _numFaces - 1) {
|
||||
_solveAnimFace = 0;
|
||||
++_solveAnimLoop;
|
||||
|
||||
if (_solveAnimLoop >= _solveAnimationNumRepeats) {
|
||||
_solveState = kWaitBeforeSound;
|
||||
_objectCurrentlyTurning = -1;
|
||||
}
|
||||
}
|
||||
|
||||
++_turnFrameID;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_objectCurrentlyTurning != -1) {
|
||||
if (g_nancy->getTotalPlayTime() > _nextTurnTime) {
|
||||
_nextTurnTime = g_nancy->getTotalPlayTime() + (_solveDelayBetweenTurns * 1000 / _currentOrder.size());
|
||||
++_turnFrameID;
|
||||
|
||||
uint faceID = _currentOrder[_objectCurrentlyTurning];
|
||||
uint frameID = _turnFrameID;
|
||||
|
||||
if (frameID == _numFramesPerTurn && (int)faceID == _numFaces - 1) {
|
||||
faceID = frameID = 0;
|
||||
}
|
||||
|
||||
// Draw clicked spindle
|
||||
drawObject(_objectCurrentlyTurning, faceID, frameID);
|
||||
|
||||
// Draw linked spindles
|
||||
for (uint i = 0; i < _links[_objectCurrentlyTurning].size(); ++i) {
|
||||
faceID = _currentOrder[_links[_objectCurrentlyTurning][i] - 1];
|
||||
frameID = _turnFrameID;
|
||||
|
||||
if (frameID == _numFramesPerTurn && (int)faceID == _numFaces - 1) {
|
||||
faceID = frameID = 0;
|
||||
}
|
||||
|
||||
drawObject(_links[_objectCurrentlyTurning][i] - 1, faceID, frameID);
|
||||
}
|
||||
|
||||
if (_turnFrameID >= _numFramesPerTurn) {
|
||||
turnLogic(_objectCurrentlyTurning);
|
||||
_objectCurrentlyTurning = -1;
|
||||
_turnFrameID = 0;
|
||||
_nextTurnTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TurningPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
uint numSpindles = stream.readUint16LE();
|
||||
_numFaces = stream.readUint16LE();
|
||||
_numFramesPerTurn = stream.readUint16LE();
|
||||
|
||||
_startPositions.resize(numSpindles);
|
||||
for (uint i = 0; i < numSpindles; ++i) {
|
||||
_startPositions[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((16 - numSpindles) * 2);
|
||||
|
||||
readRectArray(stream, _destRects, numSpindles, 16);
|
||||
readRectArray(stream, _hotspots, numSpindles, 16);
|
||||
|
||||
_separateRows = stream.readByte();
|
||||
|
||||
_startPos.x = stream.readSint32LE();
|
||||
_startPos.y = stream.readSint32LE();
|
||||
_srcIncrement.x = stream.readSint16LE();
|
||||
_srcIncrement.y = stream.readSint16LE();
|
||||
|
||||
_links.resize(numSpindles);
|
||||
for (uint i = 0; i < numSpindles; ++i) {
|
||||
for (uint j = 0; j < 4; ++j) {
|
||||
uint16 val = stream.readUint16LE();
|
||||
if (val == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
_links[i].push_back(val);
|
||||
}
|
||||
|
||||
if (_links[i].size() < 4) {
|
||||
stream.skip((4 - _links[i].size() - 1) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
stream.skip((16 - numSpindles) * 4 * 2);
|
||||
|
||||
_solveDelayBetweenTurns = stream.readUint16LE();
|
||||
_solveAnimate = stream.readByte();
|
||||
_solveAnimationNumRepeats = stream.readUint16LE();
|
||||
|
||||
_turnSound.readNormal(stream);
|
||||
|
||||
_correctOrder.resize(numSpindles);
|
||||
for (uint i = 0; i < numSpindles; ++i) {
|
||||
_correctOrder[i] = stream.readUint16LE();
|
||||
}
|
||||
stream.skip((16 - numSpindles) * 2);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TurningPuzzle::execute() {
|
||||
switch (_state) {
|
||||
case kBegin :
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_turnSound);
|
||||
_currentOrder = _startPositions;
|
||||
for (uint i = 0; i < _currentOrder.size(); ++i) {
|
||||
drawObject(i, _currentOrder[i], 0);
|
||||
}
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun :
|
||||
if (_objectCurrentlyTurning != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentOrder == _correctOrder) {
|
||||
_state = kActionTrigger;
|
||||
if (_solveAnimate) {
|
||||
_solveState = kWaitForAnimation;
|
||||
} else {
|
||||
_solveState = kWaitForSound;
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
}
|
||||
_objectCurrentlyTurning = -1;
|
||||
_turnFrameID = 0;
|
||||
_nextTurnTime = g_nancy->getTotalPlayTime() + (_solveDelayBetweenTurns * 1000 / _currentOrder.size());
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger :
|
||||
switch (_solveState) {
|
||||
case kWaitForAnimation :
|
||||
if (_nextTurnTime == 0) {
|
||||
_solveState = kWaitForSound;
|
||||
}
|
||||
return;
|
||||
case kWaitBeforeSound :
|
||||
if (_solveSoundDelayTime == 0) {
|
||||
_solveSoundDelayTime = g_nancy->getTotalPlayTime() + (_solveSoundDelay * 1000);
|
||||
} else if (g_nancy->getTotalPlayTime() > _solveSoundDelayTime) {
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
_solveState = kWaitForSound;
|
||||
}
|
||||
|
||||
return;
|
||||
case kWaitForSound :
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound) || g_nancy->_sound->isSoundPlaying(_turnSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
break;
|
||||
case kNotSolved :
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_turnSound);
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void TurningPuzzle::handleInput(NancyInput &input) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _hotspots.size(); ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
|
||||
|
||||
if (_objectCurrentlyTurning != -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
g_nancy->_sound->playSound(_turnSound);
|
||||
_objectCurrentlyTurning = i;
|
||||
}
|
||||
|
||||
// fixes nancy4 scene 4308
|
||||
input.eatMouseInput();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TurningPuzzle::drawObject(uint objectID, uint faceID, uint frameID) {
|
||||
Common::Rect srcRect = _destRects[objectID];
|
||||
srcRect.moveTo(_startPos);
|
||||
Common::Point inc(_srcIncrement.x == 1 ? srcRect.width() : _srcIncrement.x, _srcIncrement.y == -2 ? srcRect.height() : _srcIncrement.y);
|
||||
srcRect.translate( inc.x * frameID + inc.x * _numFramesPerTurn * faceID,
|
||||
_separateRows ? inc.y * objectID : 0);
|
||||
|
||||
_drawSurface.blitFrom(_image, srcRect, _destRects[objectID]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
void TurningPuzzle::turnLogic(uint objectID) {
|
||||
++_currentOrder[objectID];
|
||||
if (_currentOrder[objectID] >= _numFaces) {
|
||||
_currentOrder[objectID] = 0;
|
||||
}
|
||||
|
||||
for (uint j = 0; j < _links[objectID].size(); ++j) {
|
||||
++_currentOrder[_links[objectID][j] - 1];
|
||||
if (_currentOrder[_links[objectID][j] - 1] >= _numFaces) {
|
||||
_currentOrder[_links[objectID][j] - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
101
engines/nancy/action/puzzle/turningpuzzle.h
Normal file
101
engines/nancy/action/puzzle/turningpuzzle.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TURNINGPUZZLE_H
|
||||
#define NANCY_ACTION_TURNINGPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Handles a specific type of puzzle where clicking an object rotates it,
|
||||
// as well as several other objects linked to it. Examples are the sun/moon
|
||||
// and staircase spindle puzzles in nancy3
|
||||
class TurningPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
enum SolveState { kNotSolved, kWaitForAnimation, kWaitBeforeSound, kWaitForSound };
|
||||
TurningPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TurningPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
void updateGraphics() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TurningPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
void drawObject(uint objectID, uint faceID, uint frameID);
|
||||
void turnLogic(uint objectID);
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
uint16 _numFaces = 0;
|
||||
uint16 _numFramesPerTurn = 0;
|
||||
|
||||
Common::Array<Common::Rect> _destRects;
|
||||
Common::Array<Common::Rect> _hotspots;
|
||||
Common::Array<uint16> _startPositions;
|
||||
|
||||
bool _separateRows = false;
|
||||
|
||||
Common::Point _startPos;
|
||||
Common::Point _srcIncrement;
|
||||
|
||||
Common::Array<Common::Array<uint16>> _links;
|
||||
|
||||
uint16 _solveDelayBetweenTurns = 0;
|
||||
bool _solveAnimate = false;
|
||||
uint16 _solveAnimationNumRepeats = 0;
|
||||
|
||||
SoundDescription _turnSound;
|
||||
|
||||
Common::Array<uint16> _correctOrder;
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
Common::Array<uint16> _currentOrder;
|
||||
|
||||
uint32 _solveSoundDelayTime = 0;
|
||||
uint32 _nextTurnTime = 0;
|
||||
int32 _objectCurrentlyTurning = -1;
|
||||
uint32 _turnFrameID = 0;
|
||||
|
||||
uint32 _solveAnimLoop = 0;
|
||||
uint32 _solveAnimFace = 0;
|
||||
|
||||
SolveState _solveState = kNotSolved;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TURNINGPUZZLE_H
|
||||
181
engines/nancy/action/puzzle/twodialpuzzle.cpp
Normal file
181
engines/nancy/action/puzzle/twodialpuzzle.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/* 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/util.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/puzzledata.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "engines/nancy/action/puzzle/twodialpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void TwoDialPuzzle::init() {
|
||||
Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
|
||||
_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphics->getInputPixelFormat());
|
||||
_drawSurface.clear(g_nancy->_graphics->getTransColor());
|
||||
setTransparent(true);
|
||||
setVisible(true);
|
||||
moveTo(screenBounds);
|
||||
|
||||
g_nancy->_resource->loadImage(_imageName, _image);
|
||||
registerGraphics();
|
||||
}
|
||||
|
||||
void TwoDialPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
readFilename(stream, _imageName);
|
||||
|
||||
uint16 num1 = stream.readUint16LE();
|
||||
uint16 num2 = stream.readUint16LE();
|
||||
|
||||
_isClockwise[0] = stream.readByte();
|
||||
_isClockwise[1] = stream.readByte();
|
||||
|
||||
_startPositions[0] = stream.readUint16LE();
|
||||
_startPositions[1] = stream.readUint16LE();
|
||||
|
||||
readRect(stream, _hotspots[0]);
|
||||
readRect(stream, _hotspots[1]);
|
||||
readRect(stream, _dests[0]);
|
||||
readRect(stream, _dests[1]);
|
||||
readRectArray(stream, _srcs[0], num1, 20);
|
||||
readRectArray(stream, _srcs[1], num2, 20);
|
||||
|
||||
_correctPositions[0] = stream.readUint16LE();
|
||||
_correctPositions[1] = stream.readUint16LE();
|
||||
|
||||
_rotateSounds[0].readNormal(stream);
|
||||
_rotateSounds[1].readNormal(stream);
|
||||
|
||||
_solveScene.readData(stream);
|
||||
_solveSoundDelay = stream.readUint16LE();
|
||||
_solveSound.readNormal(stream);
|
||||
|
||||
_exitScene.readData(stream);
|
||||
readRect(stream, _exitHotspot);
|
||||
}
|
||||
|
||||
void TwoDialPuzzle::execute() {
|
||||
switch(_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
g_nancy->_sound->loadSound(_rotateSounds[0]);
|
||||
g_nancy->_sound->loadSound(_rotateSounds[1]);
|
||||
_currentPositions[0] = _startPositions[0];
|
||||
_currentPositions[1] = _startPositions[1];
|
||||
|
||||
_drawSurface.blitFrom(_image, _srcs[0][_currentPositions[0]], _dests[0]);
|
||||
_drawSurface.blitFrom(_image, _srcs[1][_currentPositions[1]], _dests[1]);
|
||||
_needsRedraw = true;
|
||||
|
||||
NancySceneState.setNoHeldItem();
|
||||
|
||||
_state = kRun;
|
||||
// fall through
|
||||
case kRun:
|
||||
if (g_nancy->_sound->isSoundPlaying(_rotateSounds[0]) || g_nancy->_sound->isSoundPlaying(_rotateSounds[1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((uint)_currentPositions[0] == _correctPositions[0] && (uint)_currentPositions[1] == _correctPositions[1]) {
|
||||
_state = kActionTrigger;
|
||||
_isSolved = true;
|
||||
_solveSoundDelayTime = g_nancy->getTotalPlayTime() + (_solveSoundDelay * 1000);
|
||||
}
|
||||
|
||||
break;
|
||||
case kActionTrigger:
|
||||
if (_isSolved) {
|
||||
if (_solveSoundDelayTime != 0) {
|
||||
if (g_nancy->getTotalPlayTime() < _solveSoundDelayTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
_solveSoundDelayTime = 0;
|
||||
g_nancy->_sound->loadSound(_solveSound);
|
||||
g_nancy->_sound->playSound(_solveSound);
|
||||
NancySceneState.setEventFlag(_solveScene._flag);
|
||||
return;
|
||||
} else {
|
||||
if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_solveSound);
|
||||
NancySceneState.changeScene(_solveScene._sceneChange);
|
||||
}
|
||||
} else {
|
||||
_exitScene.execute();
|
||||
}
|
||||
|
||||
g_nancy->_sound->stopSound(_rotateSounds[0]);
|
||||
g_nancy->_sound->stopSound(_rotateSounds[1]);
|
||||
finishExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void TwoDialPuzzle::handleInput(NancyInput &input) {
|
||||
bool canClick = (_state == kRun) && !g_nancy->_sound->isSoundPlaying(_rotateSounds[0]) && !g_nancy->_sound->isSoundPlaying(_rotateSounds[1]);
|
||||
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(g_nancy->_cursor->_puzzleExitCursor);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i <= 1; ++i) {
|
||||
if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
|
||||
g_nancy->_cursor->setCursorType(_isClockwise[i] ? CursorManager::kRotateCW : CursorManager::kRotateCCW);
|
||||
|
||||
if (canClick && input.input & NancyInput::kLeftMouseButtonUp) {
|
||||
_currentPositions[i] += _isClockwise[i] ? -1 : 1;
|
||||
|
||||
if (_currentPositions[i] < 0) {
|
||||
_currentPositions[i] = _srcs[i].size() - 1;
|
||||
} else if ((uint)_currentPositions[i] >= _srcs[i].size()) {
|
||||
_currentPositions[i] = 0;
|
||||
}
|
||||
|
||||
g_nancy->_sound->playSound(_rotateSounds[i]);
|
||||
_drawSurface.fillRect(_dests[0].findIntersectingRect(_dests[1]), _drawSurface.getTransparentColor());
|
||||
|
||||
// Blit both dials just in case
|
||||
_drawSurface.blitFrom(_image, _srcs[0][_currentPositions[0]], _dests[0]);
|
||||
_drawSurface.blitFrom(_image, _srcs[1][_currentPositions[1]], _dests[1]);
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
78
engines/nancy/action/puzzle/twodialpuzzle.h
Normal file
78
engines/nancy/action/puzzle/twodialpuzzle.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_TWODIALPUZZLE_H
|
||||
#define NANCY_ACTION_TWODIALPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Puzzle with two circular dials overlaid on top of each other. Each dial has one correct
|
||||
// position, and can only be rotated in one direction.
|
||||
class TwoDialPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
TwoDialPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~TwoDialPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "TwoDialPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Common::Path _imageName;
|
||||
|
||||
bool _isClockwise[2] = { false, false };
|
||||
uint16 _startPositions[2] = { 0, 0 };
|
||||
|
||||
Common::Rect _hotspots[2];
|
||||
Common::Rect _dests[2];
|
||||
Common::Array<Common::Rect> _srcs[2];
|
||||
|
||||
uint16 _correctPositions[2] = { 0, 0 };
|
||||
|
||||
SoundDescription _rotateSounds[2];
|
||||
|
||||
SceneChangeWithFlag _solveScene;
|
||||
uint16 _solveSoundDelay = 0;
|
||||
SoundDescription _solveSound;
|
||||
|
||||
SceneChangeWithFlag _exitScene;
|
||||
Common::Rect _exitHotspot;
|
||||
|
||||
Graphics::ManagedSurface _image;
|
||||
|
||||
int16 _currentPositions[2] = { 0, 0 };
|
||||
|
||||
bool _isSolved = false;
|
||||
uint32 _solveSoundDelayTime = 0;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_TWODIALPUZZLE_H
|
||||
66
engines/nancy/action/puzzle/whalesurvivorpuzzle.cpp
Normal file
66
engines/nancy/action/puzzle/whalesurvivorpuzzle.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/resource.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/input.h"
|
||||
#include "engines/nancy/util.h"
|
||||
|
||||
#include "engines/nancy/state/scene.h"
|
||||
#include "engines/nancy/action/puzzle/whalesurvivorpuzzle.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
void WhaleSurvivorPuzzle::init() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void WhaleSurvivorPuzzle::execute() {
|
||||
if (_state == kBegin) {
|
||||
init();
|
||||
registerGraphics();
|
||||
_state = kRun;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Stub - move to the winning screen
|
||||
warning("STUB - Whale survivor puzzle");
|
||||
NancySceneState.setEventFlag(439, g_nancy->_true); // EV_Solved_Whale_Survivor
|
||||
SceneChangeDescription scene;
|
||||
scene.sceneID = 2926;
|
||||
NancySceneState.resetStateToInit();
|
||||
NancySceneState.changeScene(scene);
|
||||
}
|
||||
|
||||
void WhaleSurvivorPuzzle::readData(Common::SeekableReadStream &stream) {
|
||||
// TODO
|
||||
stream.skip(stream.size() - stream.pos());
|
||||
}
|
||||
|
||||
void WhaleSurvivorPuzzle::handleInput(NancyInput &input) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
51
engines/nancy/action/puzzle/whalesurvivorpuzzle.h
Normal file
51
engines/nancy/action/puzzle/whalesurvivorpuzzle.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_WHALESURVIVORPUZZLE_H
|
||||
#define NANCY_ACTION_WHALESURVIVORPUZZLE_H
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
// Feeding frenzy puzzle in Nancy 9
|
||||
|
||||
class WhaleSurvivorPuzzle : public RenderActionRecord {
|
||||
public:
|
||||
WhaleSurvivorPuzzle() : RenderActionRecord(7) {}
|
||||
virtual ~WhaleSurvivorPuzzle() {}
|
||||
|
||||
void init() override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
void handleInput(NancyInput &input) override;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "WhaleSurvivorPuzzle"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_WHALESURVIVORPUZZLE_H
|
||||
265
engines/nancy/action/secondarymovie.cpp
Normal file
265
engines/nancy/action/secondarymovie.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
/* 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/graphics.h"
|
||||
#include "engines/nancy/nancy.h"
|
||||
#include "engines/nancy/sound.h"
|
||||
#include "engines/nancy/util.h"
|
||||
#include "engines/nancy/video.h"
|
||||
|
||||
#include "engines/nancy/action/secondarymovie.h"
|
||||
#include "engines/nancy/state/scene.h"
|
||||
|
||||
#include "common/serializer.h"
|
||||
|
||||
#include "video/bink_decoder.h"
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
PlaySecondaryMovie::~PlaySecondaryMovie() {
|
||||
if (NancySceneState.getActiveMovie() == this) {
|
||||
NancySceneState.setActiveMovie(nullptr);
|
||||
}
|
||||
|
||||
if (_playerCursorAllowed == kNoPlayerCursorAllowed) {
|
||||
g_nancy->setMouseEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaySecondaryMovie::readData(Common::SeekableReadStream &stream) {
|
||||
Common::Serializer ser(&stream, nullptr);
|
||||
ser.setVersion(g_nancy->getGameType());
|
||||
|
||||
readFilename(ser, _videoName);
|
||||
readFilename(ser, _paletteName, kGameTypeVampire, kGameTypeVampire);
|
||||
readFilename(ser, _bitmapOverlayName);
|
||||
|
||||
ser.syncAsUint16LE(_videoType, kGameTypeNancy7);
|
||||
ser.skip(2); // videoPlaySource
|
||||
ser.syncAsUint16LE(_videoFormat);
|
||||
ser.skip(4, kGameTypeVampire, kGameTypeVampire); // paletteStart, paletteSize
|
||||
ser.skip(2); // hasBitmapOverlaySurface
|
||||
ser.skip(2); // VIDEO_STOP_RENDERING, VIDEO_CONTINUE_RENDERING
|
||||
|
||||
ser.syncAsUint16LE(_videoSceneChange);
|
||||
ser.syncAsUint16LE(_playerCursorAllowed);
|
||||
ser.syncAsUint16LE(_playDirection);
|
||||
ser.syncAsUint16LE(_firstFrame);
|
||||
ser.syncAsUint16LE(_lastFrame);
|
||||
|
||||
if (ser.getVersion() >= kGameTypeNancy1) {
|
||||
_frameFlags.resize(15);
|
||||
for (uint i = 0; i < 15; ++i) {
|
||||
ser.syncAsSint16LE(_frameFlags[i].frameID);
|
||||
ser.syncAsSint16LE(_frameFlags[i].flagDesc.label);
|
||||
ser.syncAsUint16LE(_frameFlags[i].flagDesc.flag);
|
||||
}
|
||||
}
|
||||
|
||||
_triggerFlags.readData(stream);
|
||||
_sound.readNormal(stream);
|
||||
_sceneChange.readData(stream, ser.getVersion() == kGameTypeVampire);
|
||||
|
||||
uint16 numVideoDescs = 0;
|
||||
ser.syncAsUint16LE(numVideoDescs);
|
||||
_videoDescs.resize(numVideoDescs);
|
||||
for (uint i = 0; i < numVideoDescs; ++i) {
|
||||
_videoDescs[i].readData(stream);
|
||||
}
|
||||
|
||||
if (ser.getVersion() >= kGameTypeNancy6) {
|
||||
// Movie sound was deliberately disabled in nancy6
|
||||
_sound.name = "NO SOUND";
|
||||
}
|
||||
}
|
||||
|
||||
void PlaySecondaryMovie::init() {
|
||||
if (!_decoder) {
|
||||
if (_videoType == kVideoPlaytypeAVF) {
|
||||
_decoder.reset(new AVFDecoder());
|
||||
} else {
|
||||
_decoder.reset(new Video::BinkDecoder());
|
||||
}
|
||||
}
|
||||
|
||||
if (!_decoder->isVideoLoaded()) {
|
||||
if (!_decoder->loadFile(_videoName.append(_videoType == kVideoPlaytypeAVF ? ".avf" : ".bik"))) {
|
||||
error("Couldn't load video file %s", _videoName.toString().c_str());
|
||||
}
|
||||
|
||||
if (!_paletteName.empty()) {
|
||||
GraphicsManager::loadSurfacePalette(_fullFrame, _paletteName);
|
||||
GraphicsManager::loadSurfacePalette(_drawSurface, _paletteName);
|
||||
}
|
||||
|
||||
if (g_nancy->getGameType() == kGameTypeVampire) {
|
||||
setTransparent(true);
|
||||
_fullFrame.setTransparentColor(_drawSurface.getTransparentColor());
|
||||
|
||||
// TVD uses empty video files during the endgame ceremony
|
||||
// This makes sure the screen doesn't go black while the sound is playing
|
||||
_drawSurface.clear(_drawSurface.getTransparentColor());
|
||||
}
|
||||
}
|
||||
|
||||
_screenPosition = _drawSurface.getBounds();
|
||||
|
||||
RenderObject::init();
|
||||
}
|
||||
|
||||
void PlaySecondaryMovie::onPause(bool pause) {
|
||||
_decoder->pauseVideo(pause);
|
||||
RenderActionRecord::onPause(pause);
|
||||
}
|
||||
|
||||
void PlaySecondaryMovie::execute() {
|
||||
switch (_state) {
|
||||
case kBegin:
|
||||
init();
|
||||
registerGraphics();
|
||||
g_nancy->_sound->loadSound(_sound);
|
||||
g_nancy->_sound->playSound(_sound);
|
||||
|
||||
if (_sound.name != "NO SOUND" && g_nancy->getGameType() <= kGameTypeNancy5) {
|
||||
// Sync audio and video. This is mostly relevant for some nancy2 scenes, as the
|
||||
// devs stopped using the built-in movie sound around nancy4. The 12 ms
|
||||
// difference is roughly how long it takes for a single execution of the main game loop
|
||||
((AVFDecoder *)_decoder.get())->addFrameTime(12);
|
||||
}
|
||||
|
||||
if (_playerCursorAllowed == kNoPlayerCursorAllowed) {
|
||||
g_nancy->setMouseEnabled(false);
|
||||
}
|
||||
|
||||
NancySceneState.setActiveMovie(this);
|
||||
|
||||
_state = kRun;
|
||||
|
||||
if (Common::Rect(_decoder->getWidth(), _decoder->getHeight()) == NancySceneState.getViewport().getBounds()) {
|
||||
g_nancy->_graphics->suppressNextDraw();
|
||||
break;
|
||||
}
|
||||
|
||||
// fall through
|
||||
case kRun: {
|
||||
int newFrame = NancySceneState.getSceneInfo().frameID;
|
||||
|
||||
if (newFrame != _curViewportFrame) {
|
||||
_curViewportFrame = newFrame;
|
||||
int activeFrame = -1;
|
||||
for (uint i = 0; i < _videoDescs.size(); ++i) {
|
||||
if (newFrame == _videoDescs[i].frameID) {
|
||||
activeFrame = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (activeFrame != -1) {
|
||||
_screenPosition = _videoDescs[activeFrame].destRect;
|
||||
setVisible(true);
|
||||
} else {
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
// We update the decoder here instead of in updateGraphics() to avoid an
|
||||
// edge case in nancy4 (scene 3180) where the very last frame has a frameFlag that should trigger
|
||||
// another action record, but doesn't do so, because updateGraphics() gets called after all
|
||||
// action record execution. Instead, the movie's own scene change (which is inexplicably enabled)
|
||||
// gets triggered, and teleports the player to the wrong place instead of making them lose the game
|
||||
if (!_decoder->isPlaying() && _isVisible && !_isFinished) {
|
||||
_decoder->start();
|
||||
|
||||
if (_playDirection == kPlayMovieReverse) {
|
||||
_decoder->setRate(-_decoder->getRate());
|
||||
_decoder->seekToFrame(_lastFrame);
|
||||
} else {
|
||||
_decoder->seekToFrame(_firstFrame);
|
||||
}
|
||||
}
|
||||
|
||||
if (_decoder->needsUpdate()) {
|
||||
uint descID = 0;
|
||||
|
||||
for (uint i = 0; i < _videoDescs.size(); ++i) {
|
||||
if (_videoDescs[i].frameID == _curViewportFrame) {
|
||||
descID = i;
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsManager::copyToManaged(*_decoder->decodeNextFrame(), _fullFrame, g_nancy->getGameType() == kGameTypeVampire, _videoFormat == kSmallVideoFormat);
|
||||
_drawSurface.create(_fullFrame, _videoDescs[descID].srcRect);
|
||||
moveTo(_videoDescs[descID].destRect);
|
||||
|
||||
_needsRedraw = true;
|
||||
|
||||
for (auto &f : _frameFlags) {
|
||||
if (_decoder->getCurFrame() == f.frameID) {
|
||||
NancySceneState.setEventFlag(f.flagDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_decoder->getCurFrame() == _lastFrame && _playDirection == kPlayMovieForward) ||
|
||||
(_decoder->getCurFrame() == _firstFrame && _playDirection == kPlayMovieReverse) ||
|
||||
_decoder->endOfVideo()) {
|
||||
|
||||
// Stop the video and block it from starting again, but also wait for
|
||||
// sound to end before changing state
|
||||
_decoder->pauseVideo(true);
|
||||
_isFinished = true;
|
||||
|
||||
if (!g_nancy->_sound->isSoundPlaying(_sound)) {
|
||||
g_nancy->_sound->stopSound(_sound);
|
||||
_state = kActionTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kActionTrigger:
|
||||
_triggerFlags.execute();
|
||||
if (_videoSceneChange == kMovieSceneChange) {
|
||||
NancySceneState.changeScene(_sceneChange);
|
||||
} else {
|
||||
// Not changing the scene so enable the mouse now
|
||||
if (_playerCursorAllowed == kNoPlayerCursorAllowed) {
|
||||
g_nancy->setMouseEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
NancySceneState.setActiveMovie(nullptr);
|
||||
finishExecution();
|
||||
|
||||
// Allow looping
|
||||
if (!_isDone) {
|
||||
_isFinished = false;
|
||||
_decoder->seek(0);
|
||||
_decoder->pauseVideo(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
105
engines/nancy/action/secondarymovie.h
Normal file
105
engines/nancy/action/secondarymovie.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NANCY_ACTION_SECONDARYMOVIE_H
|
||||
#define NANCY_ACTION_SECONDARYMOVIE_H
|
||||
|
||||
#include "common/ptr.h"
|
||||
|
||||
#include "engines/nancy/action/actionrecord.h"
|
||||
|
||||
namespace Video {
|
||||
class VideoDecoder;
|
||||
}
|
||||
|
||||
namespace Nancy {
|
||||
namespace Action {
|
||||
|
||||
class InteractiveVideo;
|
||||
|
||||
// Plays an AVF or Bink video. Optionally supports:
|
||||
// - playing a sound;
|
||||
// - reverse playback;
|
||||
// - moving with the scene's background frame;
|
||||
// - hiding of player cursor (and thus, disabling input);
|
||||
// - setting event flags on a specific frame, as well as at the end of the video;
|
||||
// - changing the scene after playback ends
|
||||
// Mostly used for cinematics, with some occasional uses for background animations
|
||||
class PlaySecondaryMovie : public RenderActionRecord {
|
||||
friend class InteractiveVideo;
|
||||
public:
|
||||
static const byte kMovieSceneChange = 5;
|
||||
static const byte kMovieNoSceneChange = 6;
|
||||
|
||||
static const byte kPlayerCursorAllowed = 1;
|
||||
static const byte kNoPlayerCursorAllowed = 2;
|
||||
|
||||
static const byte kPlayMovieForward = 1;
|
||||
static const byte kPlayMovieReverse = 2;
|
||||
|
||||
struct FlagAtFrame {
|
||||
int16 frameID;
|
||||
FlagDescription flagDesc;
|
||||
};
|
||||
|
||||
PlaySecondaryMovie() : RenderActionRecord(8) {}
|
||||
virtual ~PlaySecondaryMovie();
|
||||
|
||||
void init() override;
|
||||
void onPause(bool pause) override;
|
||||
|
||||
void readData(Common::SeekableReadStream &stream) override;
|
||||
void execute() override;
|
||||
|
||||
Common::Path _videoName;
|
||||
Common::Path _paletteName;
|
||||
Common::Path _bitmapOverlayName;
|
||||
|
||||
uint16 _videoType = kVideoPlaytypeAVF;
|
||||
uint16 _videoFormat = kLargeVideoFormat;
|
||||
uint16 _videoSceneChange = kMovieNoSceneChange;
|
||||
byte _playerCursorAllowed = kPlayerCursorAllowed;
|
||||
byte _playDirection = kPlayMovieForward;
|
||||
uint16 _firstFrame = 0;
|
||||
uint16 _lastFrame = 0;
|
||||
Common::Array<FlagAtFrame> _frameFlags;
|
||||
MultiEventFlagDescription _triggerFlags;
|
||||
|
||||
SoundDescription _sound;
|
||||
|
||||
SceneChangeDescription _sceneChange;
|
||||
Common::Array<SecondaryVideoDescription> _videoDescs;
|
||||
|
||||
Common::ScopedPtr<Video::VideoDecoder> _decoder;
|
||||
|
||||
protected:
|
||||
Common::String getRecordTypeName() const override { return "PlaySecondaryMovie"; }
|
||||
bool isViewportRelative() const override { return true; }
|
||||
|
||||
Graphics::ManagedSurface _fullFrame;
|
||||
int _curViewportFrame = -1;
|
||||
bool _isFinished = false;
|
||||
};
|
||||
|
||||
} // End of namespace Action
|
||||
} // End of namespace Nancy
|
||||
|
||||
#endif // NANCY_ACTION_SECONDARYMOVIE_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user