Initial commit
This commit is contained in:
468
engines/alcachofa/game-movie-adventure.cpp
Normal file
468
engines/alcachofa/game-movie-adventure.cpp
Normal file
@@ -0,0 +1,468 @@
|
||||
/* 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 "alcachofa/alcachofa.h"
|
||||
#include "alcachofa/game.h"
|
||||
#include "alcachofa/script.h"
|
||||
|
||||
using namespace Common;
|
||||
|
||||
namespace Alcachofa {
|
||||
|
||||
static constexpr const ScriptOp kScriptOpMap[] = {
|
||||
ScriptOp::Nop,
|
||||
ScriptOp::Dup,
|
||||
ScriptOp::PushAddr,
|
||||
ScriptOp::PushValue,
|
||||
ScriptOp::Deref,
|
||||
ScriptOp::Crash, ///< would crash original engine by writing to read-only memory
|
||||
ScriptOp::PopN,
|
||||
ScriptOp::Store,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::LoadString,
|
||||
ScriptOp::LoadString, ///< exactly the same as LoadString
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::ScriptCall,
|
||||
ScriptOp::KernelCall,
|
||||
ScriptOp::JumpIfFalse,
|
||||
ScriptOp::JumpIfTrue,
|
||||
ScriptOp::Jump,
|
||||
ScriptOp::Negate,
|
||||
ScriptOp::BooleanNot,
|
||||
ScriptOp::Mul,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::Add,
|
||||
ScriptOp::Sub,
|
||||
ScriptOp::Less,
|
||||
ScriptOp::Greater,
|
||||
ScriptOp::LessEquals,
|
||||
ScriptOp::GreaterEquals,
|
||||
ScriptOp::Equals,
|
||||
ScriptOp::NotEquals,
|
||||
ScriptOp::BitAnd,
|
||||
ScriptOp::BitOr,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::Crash,
|
||||
ScriptOp::ReturnValue
|
||||
};
|
||||
|
||||
static constexpr const ScriptKernelTask kScriptKernelTaskMapV30[] = {
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::PlayVideo,
|
||||
ScriptKernelTask::PlaySound,
|
||||
ScriptKernelTask::PlayMusic,
|
||||
ScriptKernelTask::StopMusic,
|
||||
ScriptKernelTask::WaitForMusicToEnd,
|
||||
ScriptKernelTask::ShowCenterBottomText,
|
||||
ScriptKernelTask::StopAndTurn,
|
||||
ScriptKernelTask::StopAndTurnMe,
|
||||
ScriptKernelTask::ChangeCharacter,
|
||||
ScriptKernelTask::SayText,
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::Go,
|
||||
ScriptKernelTask::Put,
|
||||
ScriptKernelTask::ChangeCharacterRoom,
|
||||
ScriptKernelTask::KillProcesses,
|
||||
ScriptKernelTask::On,
|
||||
ScriptKernelTask::Off,
|
||||
ScriptKernelTask::Pickup,
|
||||
ScriptKernelTask::CharacterPickup,
|
||||
ScriptKernelTask::Drop,
|
||||
ScriptKernelTask::CharacterDrop,
|
||||
ScriptKernelTask::Delay,
|
||||
ScriptKernelTask::HadNoMousePressFor,
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::Fork,
|
||||
ScriptKernelTask::Animate,
|
||||
ScriptKernelTask::AnimateCharacter,
|
||||
ScriptKernelTask::AnimateTalking,
|
||||
ScriptKernelTask::ChangeRoom,
|
||||
ScriptKernelTask::ToggleRoomFloor,
|
||||
ScriptKernelTask::SetDialogLineReturn,
|
||||
ScriptKernelTask::DialogMenu,
|
||||
ScriptKernelTask::ClearInventory,
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::FadeType0,
|
||||
ScriptKernelTask::FadeType1,
|
||||
ScriptKernelTask::LerpWorldLodBias,
|
||||
ScriptKernelTask::FadeType2,
|
||||
ScriptKernelTask::SetActiveTextureSet,
|
||||
ScriptKernelTask::SetMaxCamSpeedFactor,
|
||||
ScriptKernelTask::WaitCamStopping,
|
||||
ScriptKernelTask::CamFollow,
|
||||
ScriptKernelTask::CamShake,
|
||||
ScriptKernelTask::LerpCamXY,
|
||||
ScriptKernelTask::LerpCamZ,
|
||||
ScriptKernelTask::LerpCamScale,
|
||||
ScriptKernelTask::LerpCamToObjectWithScale,
|
||||
ScriptKernelTask::LerpCamToObjectResettingZ,
|
||||
ScriptKernelTask::LerpCamRotation,
|
||||
ScriptKernelTask::FadeIn,
|
||||
ScriptKernelTask::FadeOut,
|
||||
ScriptKernelTask::FadeIn2,
|
||||
ScriptKernelTask::FadeOut2,
|
||||
ScriptKernelTask::LerpCamToObjectKeepingZ
|
||||
};
|
||||
|
||||
// in V3.1 there is the LerpCharacterLodBias and LerpCamXYZ tasks, no other differences
|
||||
|
||||
static constexpr const ScriptKernelTask kScriptKernelTaskMapV31[] = {
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::PlayVideo,
|
||||
ScriptKernelTask::PlaySound,
|
||||
ScriptKernelTask::PlayMusic,
|
||||
ScriptKernelTask::StopMusic,
|
||||
ScriptKernelTask::WaitForMusicToEnd,
|
||||
ScriptKernelTask::ShowCenterBottomText,
|
||||
ScriptKernelTask::StopAndTurn,
|
||||
ScriptKernelTask::StopAndTurnMe,
|
||||
ScriptKernelTask::ChangeCharacter,
|
||||
ScriptKernelTask::SayText,
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::Go,
|
||||
ScriptKernelTask::Put,
|
||||
ScriptKernelTask::ChangeCharacterRoom,
|
||||
ScriptKernelTask::KillProcesses,
|
||||
ScriptKernelTask::LerpCharacterLodBias,
|
||||
ScriptKernelTask::On,
|
||||
ScriptKernelTask::Off,
|
||||
ScriptKernelTask::Pickup,
|
||||
ScriptKernelTask::CharacterPickup,
|
||||
ScriptKernelTask::Drop,
|
||||
ScriptKernelTask::CharacterDrop,
|
||||
ScriptKernelTask::Delay,
|
||||
ScriptKernelTask::HadNoMousePressFor,
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::Fork,
|
||||
ScriptKernelTask::Animate,
|
||||
ScriptKernelTask::AnimateCharacter,
|
||||
ScriptKernelTask::AnimateTalking,
|
||||
ScriptKernelTask::ChangeRoom,
|
||||
ScriptKernelTask::ToggleRoomFloor,
|
||||
ScriptKernelTask::SetDialogLineReturn,
|
||||
ScriptKernelTask::DialogMenu,
|
||||
ScriptKernelTask::ClearInventory,
|
||||
ScriptKernelTask::Nop,
|
||||
ScriptKernelTask::FadeType0,
|
||||
ScriptKernelTask::FadeType1,
|
||||
ScriptKernelTask::LerpWorldLodBias,
|
||||
ScriptKernelTask::FadeType2,
|
||||
ScriptKernelTask::SetActiveTextureSet,
|
||||
ScriptKernelTask::SetMaxCamSpeedFactor,
|
||||
ScriptKernelTask::WaitCamStopping,
|
||||
ScriptKernelTask::CamFollow,
|
||||
ScriptKernelTask::CamShake,
|
||||
ScriptKernelTask::LerpCamXY,
|
||||
ScriptKernelTask::LerpCamZ,
|
||||
ScriptKernelTask::LerpCamScale,
|
||||
ScriptKernelTask::LerpCamToObjectWithScale,
|
||||
ScriptKernelTask::LerpCamToObjectResettingZ,
|
||||
ScriptKernelTask::LerpCamRotation,
|
||||
ScriptKernelTask::FadeIn,
|
||||
ScriptKernelTask::FadeOut,
|
||||
ScriptKernelTask::FadeIn2,
|
||||
ScriptKernelTask::FadeOut2,
|
||||
ScriptKernelTask::LerpCamXYZ,
|
||||
ScriptKernelTask::LerpCamToObjectKeepingZ
|
||||
};
|
||||
|
||||
static constexpr const char *kMapFiles[] = { // not really inherent to V3 but holds true for all V3 games
|
||||
"MAPAS/MAPA5.EMC",
|
||||
"MAPAS/MAPA4.EMC",
|
||||
"MAPAS/MAPA3.EMC",
|
||||
"MAPAS/MAPA2.EMC",
|
||||
"MAPAS/MAPA1.EMC",
|
||||
"MAPAS/GLOBAL.EMC",
|
||||
nullptr
|
||||
};
|
||||
|
||||
class GameWithVersion3 : public Game {
|
||||
public:
|
||||
Point getResolution() override {
|
||||
return Point(1024, 768);
|
||||
}
|
||||
|
||||
const char *const *getMapFiles() override {
|
||||
return kMapFiles;
|
||||
}
|
||||
|
||||
Span<const ScriptOp> getScriptOpMap() override {
|
||||
return { kScriptOpMap, ARRAYSIZE(kScriptOpMap) };
|
||||
}
|
||||
|
||||
void updateScriptVariables() override {
|
||||
Script &script = g_engine->script();
|
||||
if (g_engine->input().wasAnyMousePressed()) // yes, this variable is never reset by the engine (only by script)
|
||||
script.variable("SeHaPulsadoRaton") = 1;
|
||||
|
||||
script.variable("EstanAmbos") = g_engine->world().mortadelo().room() == g_engine->world().filemon().room();
|
||||
script.variable("textoson") = g_engine->config().subtitles() ? 1 : 0;
|
||||
}
|
||||
|
||||
void onLoadedGameFiles() override {
|
||||
// this notifies the script whether we are a demo
|
||||
if (g_engine->world().loadedMapCount() == 2)
|
||||
g_engine->script().variable("EsJuegoCompleto") = 2;
|
||||
else if (g_engine->world().loadedMapCount() == 3) // I don't know this demo
|
||||
g_engine->script().variable("EsJuegoCompleto") = 1;
|
||||
}
|
||||
|
||||
bool doesRoomHaveBackground(const Room *room) override {
|
||||
return !room->name().equalsIgnoreCase("Global") &&
|
||||
!room->name().equalsIgnoreCase("HABITACION_NEGRA");
|
||||
}
|
||||
|
||||
bool shouldCharacterTrigger(const Character *character, const char *action) override {
|
||||
// An original hack to check that bed sheet is used on the other main character only in the correct room
|
||||
// There *is* another script variable (es_casa_freddy) that should check this
|
||||
// but, I guess, Alcachofa Soft found a corner case where this does not work?
|
||||
if (scumm_stricmp(action, "iSABANA") == 0 &&
|
||||
dynamic_cast<const MainCharacter *>(character) != nullptr &&
|
||||
!character->room()->name().equalsIgnoreCase("CASA_FREDDY_ARRIBA")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Game::shouldCharacterTrigger(character, action);
|
||||
}
|
||||
|
||||
void onUserChangedCharacter() override {
|
||||
// An original bug in room POBLADO_INDIO if filemon is bound and mortadelo enters the room
|
||||
// the door A_PUENTE which was disabled is reenabled to allow mortadelo leaving
|
||||
// However if the user now changes character, the door is still enabled and filemon can
|
||||
// enter a ghost state walking through a couple rooms and softlocking.
|
||||
if (g_engine->player().currentRoom()->name().equalsIgnoreCase("POBLADO_INDIO"))
|
||||
g_engine->script().createProcess(g_engine->player().activeCharacterKind(), "ENTRAR_POBLADO_INDIO");
|
||||
}
|
||||
|
||||
bool hasMortadeloVoice(const Character *character) override {
|
||||
return Game::hasMortadeloVoice(character) ||
|
||||
character->name().equalsIgnoreCase("MORTADELO_TREN"); // an original hard-coded special case
|
||||
}
|
||||
|
||||
PointObject *unknownGoPutTarget(const Process &process, const char *action, const char *name) override {
|
||||
if (scumm_stricmp(action, "put"))
|
||||
return Game::unknownGoPutTarget(process, action, name);
|
||||
|
||||
if (!scumm_stricmp("A_Poblado_Indio", name)) {
|
||||
// A_Poblado_Indio is a Door but is originally cast into a PointObject
|
||||
// a pointer and the draw order is then interpreted as position and the character snapped onto the floor shape.
|
||||
// Instead I just use the A_Poblado_Indio1 object which exists as counter-part for A_Poblado_Indio2 which should have been used
|
||||
auto target = dynamic_cast<PointObject *>(
|
||||
g_engine->world().getObjectByName(process.character(), "A_Poblado_Indio1"));
|
||||
if (target == nullptr)
|
||||
_message("Unknown put target A_Poblado_Indio1 during exemption for A_Poblado_Indio");
|
||||
return target;
|
||||
}
|
||||
|
||||
if (!scumm_stricmp("PUNTO_VENTANA", name)) {
|
||||
// The object is in the previous, now inactive room.
|
||||
// Luckily Mortadelo already is at that point so not further action required
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!scumm_stricmp("Puerta_Casa_Freddy_Intermedia", name)) {
|
||||
// Another case of a door being cast into a PointObject
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return Game::unknownGoPutTarget(process, action, name);
|
||||
}
|
||||
|
||||
void unknownAnimateCharacterObject(const char *name) override {
|
||||
if (!scumm_stricmp(name, "COGE F DCH") || // original bug in MOTEL_ENTRADA
|
||||
!scumm_stricmp(name, "CHIQUITO_IZQ"))
|
||||
return;
|
||||
Game::unknownAnimateCharacterObject(name);
|
||||
}
|
||||
|
||||
void missingSound(const String &fileName) override {
|
||||
if (fileName == "CHAS" || fileName == "517")
|
||||
return;
|
||||
Game::missingSound(fileName);
|
||||
}
|
||||
};
|
||||
|
||||
class GameMovieAdventureSpecialV30 : public GameWithVersion3 {
|
||||
public:
|
||||
Span<const ScriptKernelTask> getScriptKernelTaskMap() override {
|
||||
return { kScriptKernelTaskMapV30, ARRAYSIZE(kScriptKernelTaskMapV30) };
|
||||
}
|
||||
|
||||
void updateScriptVariables() override {
|
||||
GameWithVersion3::updateScriptVariables();
|
||||
|
||||
// in V3.0 there is no CalcularTiempoSinPulsarRaton variable to reset the timer
|
||||
g_engine->script().setScriptTimer(g_engine->input().wasAnyMousePressed());
|
||||
}
|
||||
|
||||
bool shouldClipCamera() override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void missingAnimation(const String &fileName) override {
|
||||
static const char *exemptions[] = {
|
||||
"ANIMACION.AN0",
|
||||
"PP_MORTA.AN0",
|
||||
"ESTOMAGO.AN0",
|
||||
"CREDITOS.AN0",
|
||||
"HABITACION NEGRA.AN0",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const auto isInExemptions = [&] (const char *const *const list) {
|
||||
for (const char *const *exemption = list; *exemption != nullptr; exemption++) {
|
||||
if (fileName.equalsIgnoreCase(*exemption))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (isInExemptions(exemptions))
|
||||
debugC(1, kDebugGraphics, "Animation exemption triggered: %s", fileName.c_str());
|
||||
else
|
||||
Game::missingAnimation(fileName);
|
||||
}
|
||||
};
|
||||
|
||||
class GameMovieAdventureSpecialV31 : public GameWithVersion3 {
|
||||
public:
|
||||
Span<const ScriptKernelTask> getScriptKernelTaskMap() override {
|
||||
return { kScriptKernelTaskMapV31, ARRAYSIZE(kScriptKernelTaskMapV31) };
|
||||
}
|
||||
|
||||
void updateScriptVariables() override {
|
||||
GameWithVersion3::updateScriptVariables();
|
||||
|
||||
Script &script = g_engine->script();
|
||||
script.setScriptTimer(!script.variable("CalcularTiempoSinPulsarRaton"));
|
||||
script.variable("modored") = 0; // this is signalling whether a network connection is established
|
||||
}
|
||||
|
||||
bool shouldClipCamera() override {
|
||||
return g_engine->script().variable("EncuadrarCamara") != 0;
|
||||
}
|
||||
|
||||
void drawScreenStates() override {
|
||||
if (int32 borderWidth = g_engine->script().variable("BordesNegros")) {
|
||||
int16 width = g_system->getWidth();
|
||||
int16 height = g_system->getHeight();
|
||||
g_engine->drawQueue().add<BorderDrawRequest>(Rect(0, 0, width, borderWidth), kBlack);
|
||||
g_engine->drawQueue().add<BorderDrawRequest>(Rect(0, height - borderWidth, width, height), kBlack);
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldTriggerDoor(const Door *door) override {
|
||||
// An invalid door target, the character will go to the door and then ignore it (also in original engine)
|
||||
// this is a bug introduced in V3.1
|
||||
if (door->targetRoom() == "LABERINTO" && door->targetObject() == "a_LABERINTO_desde_LABERINTO_2")
|
||||
return false;
|
||||
return Game::shouldTriggerDoor(door);
|
||||
}
|
||||
|
||||
void missingAnimation(const String &fileName) override {
|
||||
static const char *exemptions[] = {
|
||||
"ANIMACION.AN0",
|
||||
"DESPACHO_SUPER2_OL_SOMBRAS2.AN0",
|
||||
"PP_MORTA.AN0",
|
||||
"DESPACHO_SUPER2___FONDO_PP_SUPER.AN0",
|
||||
"ESTOMAGO.AN0",
|
||||
"CREDITOS.AN0",
|
||||
"MONITOR___OL_EFECTO_FONDO.AN0",
|
||||
nullptr
|
||||
};
|
||||
|
||||
// these only happen in the german demo
|
||||
static const char *demoExemptions[] = {
|
||||
"TROZO_1.AN0",
|
||||
"TROZO_2.AN0",
|
||||
"TROZO_3.AN0",
|
||||
"TROZO_4.AN0",
|
||||
"TROZO_5.AN0",
|
||||
"TROZO_6.AN0",
|
||||
"NOTA_CINE_NEGRO.AN0",
|
||||
"PP_JOHN_WAYNE_2.AN0",
|
||||
"ARQUEOLOGO_ESTATICO_TIA.AN0",
|
||||
"ARQUEOLOGO_HABLANDO_TIA.AN0",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const auto isInExemptions = [&] (const char *const *const list) {
|
||||
for (const char *const *exemption = list; *exemption != nullptr; exemption++) {
|
||||
if (fileName.equalsIgnoreCase(*exemption))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (isInExemptions(exemptions) ||
|
||||
((g_engine->gameDescription().desc.flags & ADGF_DEMO) && isInExemptions(demoExemptions)))
|
||||
debugC(1, kDebugGraphics, "Animation exemption triggered: %s", fileName.c_str());
|
||||
else
|
||||
Game::missingAnimation(fileName);
|
||||
}
|
||||
|
||||
void unknownAnimateObject(const char *name) override {
|
||||
if (!scumm_stricmp("EXPLOSION DISFRAZ", name))
|
||||
return;
|
||||
Game::unknownAnimateObject(name);
|
||||
}
|
||||
|
||||
void unknownSayTextCharacter(const char *name, int32 dialogId) override {
|
||||
if (!scumm_stricmp(name, "OFELIA") && dialogId == 3737)
|
||||
return;
|
||||
Game::unknownSayTextCharacter(name, dialogId);
|
||||
}
|
||||
|
||||
void missingSound(const String &fileName) override {
|
||||
if ((g_engine->gameDescription().desc.flags & ADGF_DEMO) && (
|
||||
fileName == "M4996" ||
|
||||
fileName == "T40"))
|
||||
return;
|
||||
GameWithVersion3::missingSound(fileName);
|
||||
}
|
||||
|
||||
bool isKnownBadVideo(int32 videoId) override {
|
||||
return
|
||||
(videoId == 3 && (g_engine->gameDescription().desc.flags & ADGF_DEMO)) || // The german trailer is WMV-encoded
|
||||
Game::isKnownBadVideo(videoId);
|
||||
}
|
||||
|
||||
void invalidVideo(int32 videoId, const char *context) override {
|
||||
// the second intro-video is DV-encoded in the spanish steam version
|
||||
if (videoId == 1 && g_engine->gameDescription().desc.language != DE_DEU)
|
||||
warning("Could not play video %d (%s) (WMV not supported)", videoId, context);
|
||||
else
|
||||
Game::invalidVideo(videoId, context);
|
||||
}
|
||||
};
|
||||
|
||||
Game *Game::createForMovieAdventure() {
|
||||
if (g_engine->version() == EngineVersion::V3_0)
|
||||
return new GameMovieAdventureSpecialV30();
|
||||
else
|
||||
return new GameMovieAdventureSpecialV31();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user