Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

3
engines/twine/POTFILES Normal file
View File

@@ -0,0 +1,3 @@
engines/twine/detection.cpp
engines/twine/metaengine.cpp
engines/twine/parser/text.cpp

View File

@@ -0,0 +1,37 @@
/* 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 TWINE_ACHIEVEMENTS_H
#define TWINE_ACHIEVEMENTS_H
#include "engines/achievements.h"
namespace TwinE {
static const Common::AchievementDescriptionList achievementDescriptionList[] = {
{"lba", Common::STEAM_ACHIEVEMENTS, "397330"},
ACHIEVEMENT_DESC_TABLE_END_MARKER
};
} // namespace TwinE
#endif // TWINE_ACHIEVEMENTS_H

View File

@@ -0,0 +1,340 @@
/* 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 "twine/audio/music.h"
#include "audio/audiostream.h"
#include "audio/midiparser.h"
#include "audio/midiplayer.h"
#include "backends/audiocd/audiocd.h"
#include "common/debug.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/twine.h"
namespace TwinE {
/**
* LBA1 default number of tracks
* <pre>
* TRACK 01 MODE1/2352
* INDEX 01 00:00:00
* TRACK 02 AUDIO
* INDEX 01 10:47:52
* TRACK 03 AUDIO
* INDEX 01 14:02:01
* TRACK 04 AUDIO
* INDEX 01 17:02:19
* TRACK 05 AUDIO
* INDEX 01 19:34:45
* TRACK 06 AUDIO
* INDEX 01 22:22:34
* TRACK 07 AUDIO
* INDEX 01 25:09:32
* TRACK 08 AUDIO
* INDEX 01 26:47:72
* TRACK 09 AUDIO
* INDEX 01 30:29:07
* TRACK 10 AUDIO
* INDEX 01 32:04:62
* </pre>
*/
TwinEMidiPlayer::TwinEMidiPlayer(TwinEEngine *engine) : _engine(engine) {
MidiPlayer::createDriver();
int ret = _driver->open();
if (ret == 0) {
if (_nativeMT32) {
_driver->sendMT32Reset();
} else {
_driver->sendGMReset();
}
_driver->setTimerCallback(this, &timerCallback);
}
}
void TwinEMidiPlayer::play(byte *buf, int size, bool loop) {
if (_parser == nullptr) {
if (_engine->_cfgfile.MidiType == MIDIFILE_DOS) {
_parser = MidiParser::createParser_XMIDI();
} else {
_parser = MidiParser::createParser_SMF();
}
}
if (!_parser->loadMusic(buf, size)) {
warning("Failed to load midi music");
return;
}
_parser->setTrack(0);
_parser->setMidiDriver(this);
_parser->setTimerRate(_driver->getBaseTempo());
_parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
syncVolume();
debug("play midi with volume: %i", getVolume());
_isLooping = loop;
_isPlaying = true;
}
Music::Music(TwinEEngine *engine) : _engine(engine), _midiPlayer(engine) {
}
int32 Music::getLengthTrackCDR(int track) const {
// TODO:
return -1;
}
bool Music::playMidi(int32 midiIdx) {
const int32 loop = 1;
if (_engine->isDotEmuEnhanced() || _engine->isLba1Classic()) {
// the midi tracks are stored in the lba1-xx files and the adeline logo jingle
// is in lba1-32.xx - while the midiIdx is 31
const int32 trackOffset = 1;
Common::Path trackName(Common::String::format("lba1-%02i", midiIdx + trackOffset));
Audio::SeekableAudioStream *stream = Audio::SeekableAudioStream::openStreamFile(trackName);
if (stream != nullptr) {
const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
_engine->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_midiHandle,
Audio::makeLoopingAudioStream(stream, loop), volume);
debug("Play midi music track %i", midiIdx);
return true;
}
}
const char *filename;
if (_engine->_cfgfile.MidiType == MIDIFILE_DOS) {
filename = Resources::HQR_MIDI_MI_DOS_FILE;
} else if (_engine->_cfgfile.MidiType == MIDIFILE_WIN) {
filename = Resources::HQR_MIDI_MI_WIN_FILE;
} else {
debug("midi disabled - skip playing %i", midiIdx);
return false;
}
int32 midiSize = HQR::getAllocEntry(&midiPtr, filename, midiIdx);
if (midiSize == 0) {
debug("Could not find midi file for index %i", midiIdx);
return false;
}
debug("Play midi file for index %i", midiIdx);
_midiPlayer.play(midiPtr, midiSize, loop == 0 || loop > 1);
return true;
}
bool Music::playTrackCDR(int32 track) {
if (_engine->isLBA2()) {
static const char *musicTracksLba2[] = {
"",
"TADPCM1",
"TADPCM2",
"TADPCM3",
"TADPCM4",
"TADPCM5",
"JADPCM01",
"", // Track6.wav
"JADPCM02",
"JADPCM03",
"JADPCM04",
"JADPCM05",
"JADPCM06",
"JADPCM07",
"JADPCM08",
"JADPCM09",
"JADPCM10",
"JADPCM11",
"JADPCM12",
"JADPCM13",
"JADPCM14",
"JADPCM15",
"JADPCM16",
"JADPCM17",
"JADPCM18",
"LOGADPCM"};
const char *basename = musicTracksLba2[track];
Audio::SeekableAudioStream *stream = Audio::SeekableAudioStream::openStreamFile(basename);
if (stream != nullptr) {
const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
_engine->_system->getMixer()->playStream(Audio::Mixer::kMusicSoundType, &_midiHandle,
Audio::makeLoopingAudioStream(stream, 1), volume);
debug(3, "Play audio track %s for track id %i", basename, track);
return true;
}
debug(3, "Failed to find a supported format for audio track: %s", basename);
// TODO: are there versions with real audio cd?
// us release starting at track 0
// other releases at track 6
return false;
}
AudioCDManager *cdrom = g_system->getAudioCDManager();
int offset = 1;
// usually the tracks are starting at track_02.xxx for gog and steam releases.
// But some users might have ripped the original cd audio tracks and named them
// from track_01.xxx onwards. So we need to check if the first track exists.
//
// see https://bugs.scummvm.org/ticket/15410 and https://bugs.scummvm.org/ticket/14669
if (cdrom->existExtractedCDAudioFiles(1)) {
offset = 0;
}
return cdrom->play(track + offset, 1, 0, 0);
}
bool Music::initCdrom() {
AudioCDManager *cdrom = g_system->getAudioCDManager();
return cdrom->open();
}
void Music::stopMusic() {
stopMusicCD();
stopMusicMidi();
}
void Music::stopMusicCD() {
AudioCDManager *cdrom = g_system->getAudioCDManager();
cdrom->stop();
}
void Music::fadeMusicMidi(uint32 time) {
// TODO implement fade out
stopMusicMidi();
}
void Music::stopMusicMidi() {
if (_engine->isDotEmuEnhanced() || _engine->isLba1Classic() || _engine->isLBA2()) {
_engine->_system->getMixer()->stopHandle(_midiHandle);
}
_midiPlayer.stop();
free(midiPtr);
midiPtr = nullptr;
numXmi = -1;
}
bool Music::playMusic(int32 track) {
if (track == -1) {
stopMusic();
return true;
}
if (!_engine->_cfgfile.Sound) {
return false;
}
if (_engine->isCDROM()) {
if (_flagVoiceCD || track < 1 || track > 9) {
if (playMidiFile(track)) {
return true;
}
} else {
if (playCdTrack(track)) {
return true;
}
}
} else {
if (playMidiFile(track)) {
return true;
}
}
warning("Failed to play track %i", track);
return false;
}
bool Music::playMidiFile(int32 midiIdx) {
if (!_engine->_cfgfile.Sound) {
debug("sound disabled - skip playing %i", midiIdx);
return false;
}
if (_engine->isCDROM()) {
stopMusicCD();
}
if (midiIdx != numXmi || !isMidiPlaying()) {
stopMusicMidi();
numXmi = midiIdx;
if (!playMidi(midiIdx)) {
return false;
}
// volumeMidi(100);
}
return true;
}
int32 Music::getMusicCD() {
AudioCDManager *cdrom = g_system->getAudioCDManager();
// if (_engine->_system->getMillis() > endMusicCD) {
if (!cdrom->isPlaying()) {
currentMusicCD = -1;
}
return currentMusicCD;
}
bool Music::playCdTrack(int32 track) {
fadeMusicMidi(1);
numXmi = -1;
if (track != getMusicCD()) {
stopMusicCD();
// TODO: endMusicCD = _engine->toSeconds(getLengthTrackCDR(track)) / 75 + _engine->toSeconds(1);
if (playTrackCDR(track)) {
// TODO: endMusicCD += _engine->_system->getMillis();
debug("Play cd music track %i", track);
currentMusicCD = track;
}
}
return true;
}
void Music::playAllMusic(int num) {
if (num != numXmi || !isMidiPlaying()) {
stopMusicMidi();
numXmi = num;
playMidi(num);
// volumeMidi(100);
}
if (num != getMusicCD()) {
stopMusicCD();
// TODO: endMusicCD = _engine->toSeconds(getLengthTrackCDR(num)) / 75 + _engine->toSeconds(1);
if (playTrackCDR(num)) {
// TODO: endMusicCD += _engine->_system->getMillis();
currentMusicCD = num;
}
}
}
bool Music::isMidiPlaying() const {
if (_engine->isDotEmuEnhanced() || _engine->isLba1Classic()) {
return _engine->_system->getMixer()->isSoundHandleActive(_midiHandle);
}
return _midiPlayer.isPlaying();
}
void Music::musicVolume(int32 volume) {
_engine->_system->getMixer()->setVolumeForSoundType(Audio::Mixer::SoundType::kMusicSoundType, volume);
_midiPlayer.setVolume(volume);
}
} // namespace TwinE

110
engines/twine/audio/music.h Normal file
View 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 TWINE_MUSIC_H
#define TWINE_MUSIC_H
#include "audio/midiplayer.h"
#include "audio/mixer.h"
#include "common/scummsys.h"
namespace TwinE {
class TwinEEngine;
class TwinEMidiPlayer : public Audio::MidiPlayer {
private:
TwinEEngine *_engine;
public:
TwinEMidiPlayer(TwinEEngine *engine);
void play(byte *buf, int size, bool loop);
};
class Music {
private:
TwinEEngine *_engine;
TwinEMidiPlayer _midiPlayer;
void fadeMusicMidi(uint32 time = 1);
/** Auxiliar midi pointer to */
uint8 *midiPtr = nullptr;
Audio::SoundHandle _midiHandle;
/** Track number of the current playing music */
int32 numXmi = -1;
int32 currentMusicCD = -1;
// int32 endMusicCD = -1;
const bool _flagVoiceCD = false;
public:
// TODO: implement the handling
int32 _nextMusic = -1; // lba2: NextMusic
int32 _nextMusicTimer; // lba2: NextMusicTimer
bool _stopLastMusic = false; // lba2: StopLastMusic
private:
/** Stop CD music */
void stopMusicCD();
bool playMidi(int32 midiIdx);
int32 getLengthTrackCDR(int track) const;
bool playTrackCDR(int32 track);
public:
Music(TwinEEngine *engine);
/**
* Music volume
* @param current volume number
*/
void musicVolume(int32 volume);
/**
* Play CD music
* @param track track number to play
*/
bool playCdTrack(int32 track);
/**
* Generic play music, according with settings it plays CD or high quality sounds instead
* @param track track number to play
*/
bool playMusic(int32 track);
/**
* Play MIDI music
* @param midiIdx music index under mini_mi_win.hqr
* @note valid indices for lba1 are [1-32]
*/
bool playMidiFile(int32 midiIdx);
void playAllMusic(int track);
/** Stop MIDI music */
void stopMusicMidi();
/** Initialize CD-Rom */
bool initCdrom();
/** Stop MIDI and Track music */
void stopMusic();
bool isMidiPlaying() const;
int32 getMusicCD();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,319 @@
/* 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 "twine/audio/sound.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/voc.h"
#include "audio/mixer.h"
#include "common/config-manager.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/text-to-speech.h"
#include "common/types.h"
#include "common/util.h"
#include "twine/parser/text.h"
#include "twine/scene/collision.h"
#include "twine/movies.h"
#include "twine/scene/grid.h"
#include "twine/resources/hqr.h"
#include "twine/scene/movements.h"
#include "twine/resources/resources.h"
#include "twine/shared.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Sound::Sound(TwinEEngine *engine) : _engine(engine) {
}
Sound::~Sound() {
_engine->_system->getMixer()->stopAll();
}
void Sound::startRainSample() {
if (!_engine->_cfgfile.Sound) {
return;
}
#if 0
const int sample = SAMPLE_RAIN;
if (CubeMode == CUBE_EXTERIEUR && !TEMPETE_FINIE && !isSamplePlaying(sample)) {
const int rate = 0x1000;
const int offset = 300;
const int repeat = 0;
const int panning = 64;
const int volumeRain = 70;
// TODO: mixSample(sample, rate, offset, repeat, panning, volumeRain);
}
RestartRainSample = false;
#endif
}
void Sound::setChannelRate(int32 channelIdx, uint32 rate) {
if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
return;
}
_engine->_system->getMixer()->setChannelRate(_samplesPlaying[channelIdx], rate);
}
void Sound::setChannelBalance(int32 channelIdx, uint8 volumeLeft, uint8 volumeRight) {
if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
return;
}
Audio::Mixer *mixer = _engine->_system->getMixer();
const Audio::SoundHandle &handle = _samplesPlaying[channelIdx];
// balance value ranges from -127 to 127
int8 balance = (int8)(((int16)volumeRight - (int16)volumeLeft) * 127 / 255);
mixer->setChannelBalance(handle, balance);
}
void Sound::setChannelPosition(int32 channelIdx, int32 x, int32 y, int32 z) {
if (channelIdx < 0 || channelIdx >= NUM_CHANNELS) {
return;
}
const int32 camX = _engine->_grid->_startCube.x * SIZE_BRICK_XZ;
const int32 camY = _engine->_grid->_startCube.y * SIZE_BRICK_Y;
const int32 camZ = _engine->_grid->_startCube.z * SIZE_BRICK_XZ;
int32 distance = getDistance3D(camX, camY, camZ, x, y, z);
distance = boundRuleThree(0, distance, 10000, 255);
byte targetVolume = 0;
if (distance < Audio::Mixer::kMaxChannelVolume) {
targetVolume = Audio::Mixer::kMaxChannelVolume - distance;
}
_engine->_system->getMixer()->setChannelVolume(_samplesPlaying[channelIdx], targetVolume);
}
void Sound::playFlaSample(int32 index, int16 rate, int32 repeat, uint8 volumeLeft, uint8 volumeRight) {
if (!_engine->_cfgfile.Sound) {
return;
}
int channelIdx = getFreeSampleChannelIndex();
if (channelIdx == -1) {
warning("Failed to play fla sample for index: %i - no free channel", index);
return;
}
uint8 *sampPtr = nullptr;
const int32 sampSize = HQR::getAllocEntry(&sampPtr, Resources::HQR_FLASAMP_FILE, index);
if (sampSize == 0) {
warning("Failed to load %s", Resources::HQR_FLASAMP_FILE);
return;
}
// Fix incorrect sample files first byte
if (*sampPtr != 'C') {
_engine->_text->_hasHiddenVox = *sampPtr != '\0';
_engine->_text->_voxHiddenIndex++;
*sampPtr = 'C';
}
Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::YES);
Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
if (playSample(channelIdx, index, audioStream, repeat, Resources::HQR_FLASAMP_FILE)) {
setChannelRate(channelIdx, rate);
setChannelBalance(channelIdx, volumeLeft, volumeRight);
}
}
void Sound::mixSample(int32 index, uint16 pitchbend, int32 repeat, uint8 volumeLeft, uint8 volumeRight) {
mixSample3D(index, pitchbend, repeat, {}, -1);
int channelIdx = getSampleChannel(index);
setChannelBalance(channelIdx, volumeLeft, volumeRight);
}
void Sound::mixSample3D(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx) {
if (!_engine->_cfgfile.Sound) {
return;
}
int channelIdx = getFreeSampleChannelIndex();
if (channelIdx == -1) {
warning("Failed to play sample for index: %i - no free channel", index);
return;
}
if (actorIdx != -1) {
// TODO: implement balance
setChannelPosition(channelIdx, pos.x, pos.y, pos.z);
// save the actor index for the channel so we can check the position
_samplesPlayingActors[channelIdx] = actorIdx;
} else {
_samplesPlayingActors[channelIdx] = -1;
}
uint8 *sampPtr = _engine->_resources->_samplesTable[index];
uint32 sampSize = _engine->_resources->_samplesSizeTable[index];
Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::NO);
Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
playSample(channelIdx, index, audioStream, repeat, Resources::HQR_SAMPLES_FILE, Audio::Mixer::kSFXSoundType);
uint32 rate = 11025 + (pitchbend - 0x1000);
setChannelRate(channelIdx, rate);
}
bool Sound::playVoxSample(const TextEntry *text) {
if (!_engine->_cfgfile.Sound || text == nullptr) {
return false;
}
int channelIdx = getFreeSampleChannelIndex();
if (channelIdx == -1) {
warning("Failed to play vox sample for index: %i - no free channel", text->index);
return false;
}
if (_engine->isAndroid()) {
Common::Path basename(Common::String::format("%s%03i", _engine->_text->_currentOggBaseFile.c_str(), text->index));
Audio::SeekableAudioStream *audioStream = Audio::SeekableAudioStream::openStreamFile(basename);
if (audioStream != nullptr) {
return playSample(channelIdx, text->index, audioStream, 1, _engine->_text->_currentOggBaseFile.c_str(), Audio::Mixer::kSpeechSoundType);
}
}
uint8 *sampPtr = nullptr;
int32 sampSize = HQR::getAllocVoxEntry(&sampPtr, _engine->_text->_currentVoxBankFile.c_str(), text->index, _engine->_text->_voxHiddenIndex);
if (sampSize == 0) {
if (ConfMan.hasKey("tts_narrator") && ConfMan.getBool("tts_narrator")) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan != nullptr) {
ttsMan->stop();
return ttsMan->say(text->string);
}
} else {
debug(4, "TTS disabled");
}
warning("Failed to get vox sample for index: %i", text->index);
return false;
}
// Fix incorrect sample files first byte
if (*sampPtr != 'C') {
_engine->_text->_hasHiddenVox = *sampPtr != '\0';
_engine->_text->_voxHiddenIndex++;
*sampPtr = 'C';
}
Common::MemoryReadStream *stream = new Common::MemoryReadStream(sampPtr, sampSize, DisposeAfterUse::YES);
Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
return playSample(channelIdx, text->index, audioStream, 1, _engine->_text->_currentVoxBankFile.c_str(), Audio::Mixer::kSpeechSoundType);
}
bool Sound::playSample(int32 channelIdx, int32 index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType) {
if (audioStream == nullptr) {
warning("Failed to create audio stream for %s: %i", name, index);
return false;
}
// infinite loop
if (loop == -1) {
loop = 0;
}
Audio::AudioStream *loopStream = Audio::makeLoopingAudioStream(audioStream, loop);
Audio::SoundHandle *handle = &_samplesPlaying[channelIdx];
const byte volume = Audio::Mixer::kMaxChannelVolume;
_engine->_system->getMixer()->playStream(soundType, handle, loopStream, index, volume);
return true;
}
void Sound::resumeSamples() {
if (!_engine->_cfgfile.Sound) {
return;
}
_engine->_system->getMixer()->pauseAll(false);
}
void Sound::pauseSamples() {
if (!_engine->_cfgfile.Sound) {
return;
}
_engine->_system->getMixer()->pauseAll(true);
}
void Sound::stopSamples() { // HQ_StopSample
if (!_engine->_cfgfile.Sound) {
return;
}
for (int channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
_engine->_system->getMixer()->stopHandle(_samplesPlaying[channelIdx]);
}
memset(_samplesPlayingActors, -1, sizeof(_samplesPlayingActors));
}
int32 Sound::getActorChannel(int32 actorIdx) {
for (int32 channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
if (_samplesPlayingActors[channelIdx] == actorIdx) {
return channelIdx;
}
}
return -1;
}
int32 Sound::getSampleChannel(int32 index) {
for (int32 channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
if (_engine->_system->getMixer()->getSoundID(_samplesPlaying[channelIdx]) == index) {
return channelIdx;
}
}
return -1;
}
void Sound::removeChannelWatch(int32 channelIdx) {
_samplesPlayingActors[channelIdx] = -1;
}
void Sound::stopSample(int32 index) {
if (!_engine->_cfgfile.Sound) {
return;
}
const int32 channelIdx = getSampleChannel(index);
if (channelIdx != -1) {
_engine->_system->getMixer()->stopID(index);
removeChannelWatch(channelIdx);
}
}
bool Sound::isChannelPlaying(int32 channelIdx) {
if (channelIdx >= 0 && channelIdx < ARRAYSIZE(_samplesPlaying)) {
if (_engine->_system->getMixer()->isSoundHandleActive(_samplesPlaying[channelIdx])) {
return true;
}
removeChannelWatch(channelIdx);
}
return false;
}
int32 Sound::isSamplePlaying(int32 index) {
const int32 channelIdx = getSampleChannel(index);
return isChannelPlaying(channelIdx);
}
int32 Sound::getFreeSampleChannelIndex() {
for (int channelIdx = 0; channelIdx < NUM_CHANNELS; channelIdx++) {
if (!_engine->_system->getMixer()->isSoundHandleActive(_samplesPlaying[channelIdx])) {
return channelIdx;
}
}
return -1;
}
} // namespace TwinE

138
engines/twine/audio/sound.h Normal file
View 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 TWINE_SOUND_H
#define TWINE_SOUND_H
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/scummsys.h"
#include "common/types.h"
#include "twine/shared.h"
namespace TwinE {
class TextEntry;
#define NUM_CHANNELS 32
namespace Samples {
enum _Samples {
TwinsenHit = 0,
SoldierHit = 4,
ItemPopup = 11,
Explode = 37,
BigItemFound = 41,
TaskCompleted = 41,
Hit = 86,
ItemFound = 97,
WalkFloorBegin = 126, // BASE_STEP_SOUND
WalkFloorRightBegin = 141
};
}
class TwinEEngine;
class Sound {
private:
TwinEEngine *_engine;
/** Samples playing at the same time */
Audio::SoundHandle _samplesPlaying[NUM_CHANNELS];
/** Samples playing at a actors position */
int32 _samplesPlayingActors[NUM_CHANNELS]{0};
bool playSample(int32 channelIdx, int32 index, Audio::SeekableAudioStream *audioStream, int32 loop, const char *name, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
bool isChannelPlaying(int32 channelIdx);
/** Find a free channel slot to use */
int32 getFreeSampleChannelIndex();
/** Remove a sample from the channel usage list */
void removeChannelWatch(int32 channelIdx);
public:
int32 _parmSampleVolume = 127;
int32 _parmSampleDecalage = 0;
int32 _parmSampleFrequence = 0;
Sound(TwinEEngine *engine);
~Sound();
/** Get the channel where the sample is playing */
int32 getSampleChannel(int32 index);
/**
* Play FLA movie samples
* @param index sample index under flasamp.hqr file
* @param repeat number of times to repeat the sample
*/
void playFlaSample(int32 index, int16 rate, int32 repeat, uint8 volumeLeft, uint8 volumeRight);
void setChannelBalance(int32 channelIdx, uint8 volumeLeft, uint8 volumeRight);
void setChannelRate(int32 channelIdx, uint32 rate);
/** Update sample position in channel */
void setChannelPosition(int32 channelIdx, int32 x, int32 y, int32 z);
inline void setChannelPosition(int32 channelIdx, const IVec3 &pos) {
setChannelPosition(channelIdx, pos.x, pos.y, pos.z);
}
/**
* Play samples
* @param index sample index under flasamp.hqr file
* @param repeat number of times to repeat the sample
* @param pos sound generating entity position
* @param actorIdx
*/
void mixSample3D(int32 index, uint16 pitchbend, int32 repeat, const IVec3 &pos, int32 actorIdx); // HQ_3D_MixSample
void mixSample(int32 index, uint16 pitchbend, int32 repeat, uint8 volumeLeft, uint8 volumeRight); // HQ_MixSample
/** Pause samples */
void pauseSamples();
/** Resume samples */
void resumeSamples();
void startRainSample();
/** Stop samples */
void stopSamples();
/** Get the channel where the actor sample is playing */
int32 getActorChannel(int32 actorIdx);
/** Stops a specific sample */
void stopSample(int32 index); // HQ_StopOneSample
/** Check if a sample is playing */
int32 isSamplePlaying(int32 index);
/** Play VOX sample */
bool playVoxSample(const TextEntry *text);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine twine "Little Big Adventure" yes "" "" "highres" "imgui midi gif"

12
engines/twine/credits.pl Normal file
View File

@@ -0,0 +1,12 @@
begin_section("TwinE");
add_person("Alexandre Fontoura", "xesf", "(retired)");
add_person("Vincent Hamm", "yaz0r", "(retired)");
add_person("Felipe Sanches", "jucablues", "(retired)");
add_person("Nikita Tereshin", "rumkex", "(retired)");
add_person("Patrik Dahlstr&ouml;m", "risca", "(retired)");
add_person("Arthur Blot", "arthur.blot78", "(retired)");
add_person("Kyuubu", "wett", "(retired)");
add_person("To&euml;l Hartmann", "toel__", "(retired)");
add_person("Seb&aacute;stien Viannay", "", "(retired)");
add_person("Martin Gerhardy", "mgerhardy", "");
end_section();

View File

@@ -0,0 +1,475 @@
/* 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 "twine/debugger/console.h"
#include "common/scummsys.h"
#include "common/util.h"
#include "twine/debugger/debug_state.h"
#include "twine/holomap.h"
#include "twine/renderer/redraw.h"
#include "twine/resources/hqr.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/scene.h"
#include "twine/renderer/screens.h"
#include "twine/text.h"
#include "twine/twine.h"
#include "twine/audio/music.h"
namespace TwinE {
TwinEConsole::TwinEConsole(TwinEEngine *engine) : _engine(engine), GUI::Debugger() {
registerCmd("give_item", WRAP_METHOD(TwinEConsole, doGiveItem));
registerCmd("give_allitems", WRAP_METHOD(TwinEConsole, doGiveAllItems));
registerCmd("give_key", WRAP_METHOD(TwinEConsole, doGiveKey));
registerCmd("give_gas", WRAP_METHOD(TwinEConsole, doGiveGas));
registerCmd("give_kashes", WRAP_METHOD(TwinEConsole, doGiveKashes));
registerCmd("play_video", WRAP_METHOD(TwinEConsole, doPlayVideo));
registerCmd("play_midi", WRAP_METHOD(TwinEConsole, doPlayMidi));
registerCmd("play_music", WRAP_METHOD(TwinEConsole, doPlayMusic));
registerCmd("change_scene", WRAP_METHOD(TwinEConsole, doChangeScene));
registerCmd("change_chapter", WRAP_METHOD(TwinEConsole, doChangeChapter));
registerCmd("toggle_scenery_view", WRAP_METHOD(TwinEConsole, doToggleSceneryView));
registerCmd("magic_points", WRAP_METHOD(TwinEConsole, doAddMagicPoints));
registerCmd("dumpfile", WRAP_METHOD(TwinEConsole, doDumpFile));
registerCmd("toggle_zones", WRAP_METHOD(TwinEConsole, doToggleZoneRendering));
registerCmd("toggle_tracks", WRAP_METHOD(TwinEConsole, doToggleTrackRendering));
registerCmd("toggle_godmode", WRAP_METHOD(TwinEConsole, doToggleGodMode));
registerCmd("toggle_autoagressive", WRAP_METHOD(TwinEConsole, doToggleAutoAggressive));
registerCmd("toggle_actors", WRAP_METHOD(TwinEConsole, doToggleActorRendering));
registerCmd("toggle_clips", WRAP_METHOD(TwinEConsole, doToggleClipRendering));
registerCmd("toggle_freecamera", WRAP_METHOD(TwinEConsole, doToggleFreeCamera));
registerCmd("toggle_scenerendering", WRAP_METHOD(TwinEConsole, doToggleSceneRendering));
registerCmd("set_track_obj", WRAP_METHOD(TwinEConsole, doSetTrackObject));
registerCmd("scene_actor", WRAP_METHOD(TwinEConsole, doSkipSceneActorsBut));
registerCmd("hero_pos", WRAP_METHOD(TwinEConsole, doSetHeroPosition));
registerCmd("set_life", WRAP_METHOD(TwinEConsole, doSetLife));
registerCmd("set_game_flag", WRAP_METHOD(TwinEConsole, doSetGameFlag));
registerCmd("show_game_flag", WRAP_METHOD(TwinEConsole, doPrintGameFlag));
registerCmd("set_inventory_flag", WRAP_METHOD(TwinEConsole, doSetInventoryFlag));
registerCmd("show_inventory_flag", WRAP_METHOD(TwinEConsole, doPrintInventoryFlag));
registerCmd("set_holomap_flag", WRAP_METHOD(TwinEConsole, doSetHolomapFlag));
registerCmd("set_holomap_trajectory", WRAP_METHOD(TwinEConsole, doSetHolomapTrajectory));
registerCmd("show_holomap_flag", WRAP_METHOD(TwinEConsole, doPrintHolomapFlag));
}
TwinEConsole::~TwinEConsole() {
}
void TwinEConsole::preEnter() {
_engine->_input->resetActionStates();
Super::preEnter();
}
void TwinEConsole::postEnter() {
_engine->_input->resetActionStates();
Super::postEnter();
}
#define TOGGLE_DEBUG(var, description) \
if ((var)) { \
debugPrintf("Disabling " description); \
(var) = false; \
} else { \
debugPrintf("Enabling " description); \
(var) = true; \
}
bool TwinEConsole::doToggleZoneRendering(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_debugState->_showingZones, "zone rendering\n")
return true;
}
bool TwinEConsole::doToggleActorRendering(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_debugState->_showingActors, "actor rendering\n")
return true;
}
bool TwinEConsole::doToggleTrackRendering(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_debugState->_showingTracks, "tracks rendering\n")
return true;
}
bool TwinEConsole::doToggleGodMode(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_debugState->_godMode, "god mode\n")
return true;
}
bool TwinEConsole::doToggleClipRendering(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_debugState->_showingClips, "clip rendering\n")
return true;
}
bool TwinEConsole::doToggleSceneryView(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_redraw->_flagMCGA, "scenery view\n")
return true;
}
bool TwinEConsole::doToggleAutoAggressive(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_actor->_combatAuto, "auto aggressive\n")
return true;
}
bool TwinEConsole::doAddMagicPoints(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: specify the magic points\n");
return true;
}
const int16 magicPoints = atoi(argv[1]);
_engine->_gameState->_magicLevelIdx = CLIP<int16>(magicPoints, 0, 4);
_engine->_gameState->setMaxMagicPoints();
return true;
}
bool TwinEConsole::doSkipSceneActorsBut(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: give actor id of scene or -1 to disable\n");
return true;
}
const int16 actorIdx = atoi(argv[1]);
debugPrintf("Only load actor %d in the next scene\n", actorIdx);
_engine->_debugState->_onlyLoadActor = actorIdx;
return true;
}
bool TwinEConsole::doToggleFreeCamera(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_debugState->_useFreeCamera, "free camera movement\n")
return true;
}
bool TwinEConsole::doSetTrackObject(int argc, const char **argv) {
if (argc <= 2) {
debugPrintf("Expected to get a the scene actor number and the track\n");
return true;
}
const int32 otherActorIdx = atoi(argv[1]);
const int32 offset = atoi(argv[2]);
_engine->_scene->getActor(otherActorIdx)->_offsetTrack = offset;
return true;
}
bool TwinEConsole::doToggleSceneRendering(int argc, const char **argv) {
TOGGLE_DEBUG(_engine->_debugState->_disableGridRendering, "scene rendering\n")
return true;
}
bool TwinEConsole::doSetInventoryFlag(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a inventory flag index as first parameter\n");
return true;
}
const uint8 idx = atoi(argv[1]);
if (idx >= NUM_INVENTORY_ITEMS) {
debugPrintf("given index exceeds the max allowed value of %i\n", NUM_INVENTORY_ITEMS - 1);
return true;
}
const uint8 val = argc == 3 ? atoi(argv[2]) : 0;
_engine->_gameState->_inventoryFlags[idx] = val;
return true;
}
bool TwinEConsole::doSetHolomapTrajectory(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a holomap trajectory index as parameter\n");
return true;
}
_engine->_scene->_numHolomapTraj = atoi(argv[1]);
_engine->_scene->reloadCurrentScene();
return false;
}
bool TwinEConsole::doSetHolomapFlag(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a holomap flag index as first parameter. Use -1 to set all flags\n");
return true;
}
GameState* state = _engine->_gameState;
state->setGameFlag(InventoryItems::kiHolomap, 1);
state->_inventoryFlags[InventoryItems::kiHolomap] = 1;
state->setGameFlag(GAMEFLAG_INVENTORY_DISABLED, 0);
const int idx = atoi(argv[1]);
if (idx == -1) {
for (int i = 0; i < _engine->numHoloPos(); ++i) {
_engine->_holomap->setHoloPos(i);
}
return true;
}
if (idx < 0 || idx >= _engine->numHoloPos()) {
debugPrintf("given index exceeds the max allowed value of %i\n", _engine->numHoloPos() - 1);
return true;
}
_engine->_holomap->setHoloPos(idx);
return true;
}
bool TwinEConsole::doSetGameFlag(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a game flag index as first parameter\n");
return true;
}
const uint8 idx = atoi(argv[1]);
const uint8 val = argc == 3 ? atoi(argv[2]) : 0;
_engine->_gameState->setGameFlag(idx, val);
return true;
}
bool TwinEConsole::doPrintGameFlag(int argc, const char **argv) {
if (argc <= 1) {
for (int i = 0; i < NUM_GAME_FLAGS; ++i) {
debugPrintf("[%03d] = %d\n", i, _engine->_gameState->hasGameFlag(i));
}
return true;
}
const uint8 idx = atoi(argv[1]);
debugPrintf("[%03d] = %d\n", idx, _engine->_gameState->hasGameFlag(idx));
return true;
}
bool TwinEConsole::doPrintInventoryFlag(int argc, const char **argv) {
if (argc <= 1) {
for (int i = 0; i < NUM_INVENTORY_ITEMS; ++i) {
debugPrintf("[%03d] = %d\n", i, _engine->_gameState->_inventoryFlags[i]);
}
return true;
}
const uint8 idx = atoi(argv[1]);
if (idx < NUM_INVENTORY_ITEMS) {
debugPrintf("[%03d] = %d\n", idx, _engine->_gameState->_inventoryFlags[idx]);
}
return true;
}
bool TwinEConsole::doPrintHolomapFlag(int argc, const char **argv) {
if (argc <= 1) {
for (int i = 0; i < _engine->numHoloPos(); ++i) {
debugPrintf("[%03d] = %d\n", i, _engine->_gameState->_holomapFlags[i]);
}
return true;
}
const uint16 idx = atoi(argv[1]);
if (idx < _engine->numHoloPos()) {
debugPrintf("[%03d] = %d\n", idx, _engine->_gameState->_holomapFlags[idx]);
}
return true;
}
bool TwinEConsole::doGiveKey(int argc, const char **argv) {
int amount = 1;
if (argc >= 2) {
amount = atoi(argv[1]);
}
_engine->_gameState->addKeys(amount);
return true;
}
bool TwinEConsole::doGiveGas(int argc, const char **argv) {
int amount = 1;
if (argc >= 2) {
amount = atoi(argv[1]);
}
_engine->_gameState->addGas(amount);
return true;
}
bool TwinEConsole::doGiveKashes(int argc, const char **argv) {
int amount = 1;
if (argc >= 2) {
amount = atoi(argv[1]);
}
_engine->_gameState->addKashes(amount);
return true;
}
bool TwinEConsole::doSetHeroPosition(int argc, const char **argv) {
IVec3 &pos = _engine->_scene->_sceneHero->_posObj;
if (argc < 4) {
debugPrintf("Current hero position: %i:%i:%i\n", pos.x, pos.y, pos.z);
return true;
}
pos.x = atoi(argv[1]);
pos.y = atoi(argv[2]);
pos.z = atoi(argv[3]);
return true;
}
bool TwinEConsole::doPlayVideo(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a video filename as first parameter\n");
return true;
}
_engine->queueMovie(argv[1]);
return true;
}
bool TwinEConsole::doPlayMidi(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a midi id as first parameter\n");
return true;
}
int newMidiIndex = atoi(argv[1]);
_engine->_music->playMidiFile(newMidiIndex);
return true;
}
bool TwinEConsole::doPlayMusic(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a music track id as first parameter\n");
return true;
}
int newMusicTrackIndex = atoi(argv[1]);
_engine->_music->playMusic(newMusicTrackIndex);
return true;
}
bool TwinEConsole::doChangeScene(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a scene index as first parameter\n");
return true;
}
byte newSceneIndex = atoi(argv[1]);
if (newSceneIndex >= LBA1SceneId::SceneIdMax) {
debugPrintf("Scene index out of bounds\n");
return true;
}
_engine->_scene->_newCube = atoi(argv[1]);
_engine->_scene->_flagChgCube = ScenePositionType::kScene;
_engine->_scene->changeCube();
return true;
}
bool TwinEConsole::doChangeChapter(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get a chapter index as first parameter\n");
return true;
}
debugPrintf("Old chapter was: %i\n", _engine->_gameState->getChapter());
_engine->_gameState->setChapter(atoi(argv[1]));
return true;
}
bool TwinEConsole::doDumpFile(int argc, const char **argv) {
if (argc <= 2) {
debugPrintf("Expected to get a a hqr file and an index\n");
return true;
}
const char *hqr = argv[1];
const int index = atoi(argv[2]);
const Common::String &targetFileName = Common::String::format("dumps/%03i-%s.dump", index, hqr);
HQR::dumpEntry(hqr, index, targetFileName.c_str());
return true;
}
static const char *ItemNames[] = {
"Holomap",
"MagicBall",
"UseSabre",
"GawleysHorn",
"Tunic",
"BookOfBu",
"SendellsMedallion",
"FlaskOfClearWater",
"RedCard",
"BlueCard",
"IDCard",
"MrMiesPass",
"ProtoPack",
"Snowboard",
"Penguin",
"GasItem",
"PirateFlag",
"MagicFlute",
"SpaceGuitar",
"HairDryer",
"AncesteralKey",
"BottleOfSyrup",
"EmptyBottle",
"FerryTicket",
"Keypad",
"CoffeeCan",
"BonusList",
"CloverLeaf"
};
static_assert(ARRAYSIZE(ItemNames) == InventoryItems::MaxInventoryItems, "Array size doesn't match items");
bool TwinEConsole::doGiveItem(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get an item as first parameter\n");
for (int i = 0; i < ARRAYSIZE(ItemNames); ++i) {
debugPrintf(" - %2i: %s\n", i, ItemNames[i]);
}
return true;
}
byte itemIdx = atoi(argv[1]);
if (itemIdx >= InventoryItems::MaxInventoryItems) {
debugPrintf("Item index out of bounds\n");
return true;
}
GameState* state = _engine->_gameState;
state->setGameFlag(itemIdx, 1);
state->_inventoryFlags[itemIdx] = 1;
state->setGameFlag(GAMEFLAG_INVENTORY_DISABLED, 0);
return true;
}
bool TwinEConsole::doGiveAllItems(int argc, const char **argv) {
GameState* state = _engine->_gameState;
for (int32 i = 0; i < NUM_INVENTORY_ITEMS; ++i) {
state->setGameFlag(i, 1);
state->_inventoryFlags[i] = 1;
}
state->setGameFlag(GAMEFLAG_INVENTORY_DISABLED, 0);
int amount = 1;
if (argc >= 2) {
amount = atoi(argv[1]);
}
state->addKeys(amount);
state->addLeafBoxes(amount);
state->addKashes(amount);
state->addLeafs(amount);
state->addMagicPoints(amount);
state->addGas(amount);
return true;
}
bool TwinEConsole::doSetLife(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get the life points as parameter\n");
return true;
}
_engine->_scene->_sceneHero->setLife(atoi(argv[1]));
return true;
}
} // End of namespace TwinE

View 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 TWINE_CONSOLE_H
#define TWINE_CONSOLE_H
#include "gui/debugger.h"
#include "twine/scene/gamestate.h"
namespace TwinE {
class TwinEEngine;
class TwinEConsole : public GUI::Debugger {
private:
using Super = GUI::Debugger;
TwinEEngine *_engine;
bool doToggleSceneryView(int argc, const char **argv);
bool doPlayVideo(int argc, const char **argv);
bool doPlayMidi(int argc, const char **argv);
bool doPlayMusic(int argc, const char **argv);
bool doPrintGameFlag(int argc, const char **argv);
bool doPrintInventoryFlag(int argc, const char **argv);
bool doPrintHolomapFlag(int argc, const char **argv);
bool doSetHeroPosition(int argc, const char **argv);
bool doGiveItem(int argc, const char **argv);
bool doSetLife(int argc, const char **argv);
bool doGiveAllItems(int argc, const char **argv);
bool doChangeScene(int argc, const char **argv);
bool doToggleAutoAggressive(int argc, const char **argv);
bool doGiveKey(int argc, const char **argv);
bool doGiveGas(int argc, const char **argv);
bool doGiveKashes(int argc, const char **argv);
bool doToggleZoneRendering(int argc, const char **argv);
bool doToggleClipRendering(int argc, const char **argv);
bool doToggleActorRendering(int argc, const char **argv);
bool doToggleTrackRendering(int argc, const char **argv);
bool doToggleGodMode(int argc, const char **argv);
bool doToggleFreeCamera(int argc, const char **argv);
bool doToggleSceneRendering(int argc, const char **argv);
bool doSetTrackObject(int argc, const char **argv);
bool doChangeChapter(int argc, const char **argv);
bool doSkipSceneActorsBut(int argc, const char **argv);
bool doSetGameFlag(int argc, const char **argv);
bool doSetInventoryFlag(int argc, const char **argv);
bool doSetHolomapFlag(int argc, const char **argv);
bool doAddMagicPoints(int argc, const char **argv);
bool doDumpFile(int argc, const char **argv);
bool doSetHolomapTrajectory(int argc, const char **argv);
protected:
void preEnter() override;
void postEnter() override;
public:
TwinEConsole(TwinEEngine *engine);
~TwinEConsole() override;
bool exec(const char *file) {
const char *argv[] = {"", file};
return cmdExecFile(2, argv);
}
};
} // End of namespace TwinE
#endif // TWINE_CONSOLE_H

View File

@@ -0,0 +1,237 @@
/* 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 "twine/debugger/debug_state.h"
#include "common/util.h"
#include "twine/menu/interface.h"
#include "twine/menu/menu.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
DebugState::DebugState(TwinEEngine *engine) : _engine(engine) {}
void DebugState::update() {
changeGridCamera();
}
void DebugState::drawClip(const Common::Rect &rect) {
if (!_showingClips) {
return;
}
_engine->_menu->drawRectBorders(rect);
}
void DebugState::projectBoundingBoxPoints(IVec3 *pPoint3d, IVec3 *pPoint3dProjected) {
*pPoint3dProjected = _engine->_renderer->projectPoint(*pPoint3d);
}
bool DebugState::checkZoneType(ZoneType type) const {
return (_typeZones & (1u << (uint32)type)) != 0u;
}
DebugState::ScenePositionsProjected DebugState::calculateBoxPositions(const IVec3 &mins, const IVec3 &maxs) {
ScenePositionsProjected positions;
// compute the points in 3D
positions.frontBottomLeftPoint.x = mins.x - _engine->_grid->_worldCube.x;
positions.frontBottomLeftPoint.y = mins.y - _engine->_grid->_worldCube.y;
positions.frontBottomLeftPoint.z = maxs.z - _engine->_grid->_worldCube.z;
positions.frontBottomRightPoint.x = maxs.x - _engine->_grid->_worldCube.x;
positions.frontBottomRightPoint.y = mins.y - _engine->_grid->_worldCube.y;
positions.frontBottomRightPoint.z = maxs.z - _engine->_grid->_worldCube.z;
positions.frontTopLeftPoint.x = mins.x - _engine->_grid->_worldCube.x;
positions.frontTopLeftPoint.y = maxs.y - _engine->_grid->_worldCube.y;
positions.frontTopLeftPoint.z = maxs.z - _engine->_grid->_worldCube.z;
positions.frontTopRightPoint = maxs - _engine->_grid->_worldCube;
positions.backBottomLeftPoint = mins - _engine->_grid->_worldCube;
positions.backBottomRightPoint.x = maxs.x - _engine->_grid->_worldCube.x;
positions.backBottomRightPoint.y = mins.y - _engine->_grid->_worldCube.y;
positions.backBottomRightPoint.z = mins.z - _engine->_grid->_worldCube.z;
positions.backTopLeftPoint.x = mins.x - _engine->_grid->_worldCube.x;
positions.backTopLeftPoint.y = maxs.y - _engine->_grid->_worldCube.y;
positions.backTopLeftPoint.z = mins.z - _engine->_grid->_worldCube.z;
positions.backTopRightPoint.x = maxs.x - _engine->_grid->_worldCube.x;
positions.backTopRightPoint.y = maxs.y - _engine->_grid->_worldCube.y;
positions.backTopRightPoint.z = mins.z - _engine->_grid->_worldCube.z;
// project all points
projectBoundingBoxPoints(&positions.frontBottomLeftPoint, &positions.frontBottomLeftPoint2D);
projectBoundingBoxPoints(&positions.frontBottomRightPoint, &positions.frontBottomRightPoint2D);
projectBoundingBoxPoints(&positions.frontTopLeftPoint, &positions.frontTopLeftPoint2D);
projectBoundingBoxPoints(&positions.frontTopRightPoint, &positions.frontTopRightPoint2D);
projectBoundingBoxPoints(&positions.backBottomLeftPoint, &positions.backBottomLeftPoint2D);
projectBoundingBoxPoints(&positions.backBottomRightPoint, &positions.backBottomRightPoint2D);
projectBoundingBoxPoints(&positions.backTopLeftPoint, &positions.backTopLeftPoint2D);
projectBoundingBoxPoints(&positions.backTopRightPoint, &positions.backTopRightPoint2D);
return positions;
}
bool DebugState::drawBox(const ScenePositionsProjected &positions, uint8 color) {
bool state = false;
// draw front part
state |= _engine->_interface->drawLine(positions.frontBottomLeftPoint2D.x, positions.frontBottomLeftPoint2D.y, positions.frontTopLeftPoint2D.x, positions.frontTopLeftPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.frontTopLeftPoint2D.x, positions.frontTopLeftPoint2D.y, positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, positions.frontBottomRightPoint2D.x, positions.frontBottomRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.frontBottomRightPoint2D.x, positions.frontBottomRightPoint2D.y, positions.frontBottomLeftPoint2D.x, positions.frontBottomLeftPoint2D.y, color);
// draw top part
state |= _engine->_interface->drawLine(positions.frontTopLeftPoint2D.x, positions.frontTopLeftPoint2D.y, positions.backTopLeftPoint2D.x, positions.backTopLeftPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.backTopLeftPoint2D.x, positions.backTopLeftPoint2D.y, positions.backTopRightPoint2D.x, positions.backTopRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.backTopRightPoint2D.x, positions.backTopRightPoint2D.y, positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, positions.frontTopLeftPoint2D.x, positions.frontTopLeftPoint2D.y, color);
// draw back part
state |= _engine->_interface->drawLine(positions.backBottomLeftPoint2D.x, positions.backBottomLeftPoint2D.y, positions.backTopLeftPoint2D.x, positions.backTopLeftPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.backTopLeftPoint2D.x, positions.backTopLeftPoint2D.y, positions.backTopRightPoint2D.x, positions.backTopRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.backTopRightPoint2D.x, positions.backTopRightPoint2D.y, positions.backBottomRightPoint2D.x, positions.backBottomRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.backBottomRightPoint2D.x, positions.backBottomRightPoint2D.y, positions.backBottomLeftPoint2D.x, positions.backBottomLeftPoint2D.y, color);
// draw bottom part
state |= _engine->_interface->drawLine(positions.frontBottomLeftPoint2D.x, positions.frontBottomLeftPoint2D.y, positions.backBottomLeftPoint2D.x, positions.backBottomLeftPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.backBottomLeftPoint2D.x, positions.backBottomLeftPoint2D.y, positions.backBottomRightPoint2D.x, positions.backBottomRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.backBottomRightPoint2D.x, positions.backBottomRightPoint2D.y, positions.frontBottomRightPoint2D.x, positions.frontBottomRightPoint2D.y, color);
state |= _engine->_interface->drawLine(positions.frontBottomRightPoint2D.x, positions.frontBottomRightPoint2D.y, positions.frontBottomLeftPoint2D.x, positions.frontBottomLeftPoint2D.y, color);
return state;
}
void DebugState::changeGridCamera() {
if (!_useFreeCamera) {
return;
}
Grid *grid = _engine->_grid;
Redraw *redraw = _engine->_redraw;
Input *input = _engine->_input;
if (input->isActionActive(TwinEActionType::DebugGridCameraPressUp)) {
grid->_startCube.z--;
redraw->_firstTime = true;
} else if (input->isActionActive(TwinEActionType::DebugGridCameraPressDown)) {
grid->_startCube.z++;
redraw->_firstTime = true;
}
if (input->isActionActive(TwinEActionType::DebugGridCameraPressLeft)) {
grid->_startCube.x--;
redraw->_firstTime = true;
} else if (input->isActionActive(TwinEActionType::DebugGridCameraPressRight)) {
grid->_startCube.x++;
redraw->_firstTime = true;
}
}
bool DebugState::displayActors() {
bool state = false;
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
const ActorStruct *actorPtr = _engine->_scene->getActor(a);
const IVec3 &pos = actorPtr->posObj();
const BoundingBox &bbox = actorPtr->_boundingBox;
const ScenePositionsProjected &positions = calculateBoxPositions(pos + bbox.mins, pos + bbox.maxs);
if (!drawBox(positions, COLOR_WHITE)) {
continue;
}
const int boxwidth = 75;
const int lineHeight = 14;
const Common::Rect filledRect(positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, positions.frontTopRightPoint2D.x + boxwidth, positions.frontTopRightPoint2D.y + lineHeight);
_engine->_interface->box(filledRect, COLOR_WHITE);
_engine->_menu->drawRectBorders(filledRect);
_engine->drawText(positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, Common::String::format("Actor: %i", a), true, false, boxwidth);
const int16 rleft = positions.frontTopLeftPoint2D.x;
const int16 rtop = positions.backTopLeftPoint2D.y;
const int16 rright = positions.backTopRightPoint2D.x;
const int16 rbottom = positions.frontBottomRightPoint2D.y;
Common::Rect actorRect(rleft, rtop, rright, rbottom);
actorRect.extend(filledRect);
_engine->_redraw->addPhysBox(actorRect);
state = true;
}
return state;
}
// TODO: implement the rendering points of all tracks as a dot with the id
bool DebugState::displayTracks() {
#if 0
for (int i = 0; i < _engine->_scene->sceneNumTracks; i++) {
const Vec3 *trackPoint = &_engine->_scene->sceneTracks[i];
}
#endif
return false;
}
bool DebugState::displayZones() {
bool state = false;
for (int i = 0; i < _engine->_scene->_sceneNumZones; i++) {
const ZoneStruct *zonePtr = &_engine->_scene->_sceneZones[i];
if (!checkZoneType(zonePtr->type)) {
continue;
}
const ScenePositionsProjected &positions = calculateBoxPositions(zonePtr->mins, zonePtr->maxs);
const uint8 color = 15 * 3 + (int)zonePtr->type * 16;
if (!drawBox(positions, color)) {
continue;
}
const int boxwidth = 50;
const int lineHeight = 14;
const Common::Rect filledRect(positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, positions.frontTopRightPoint2D.x + boxwidth, positions.frontTopRightPoint2D.y + lineHeight);
_engine->_interface->box(filledRect, COLOR_WHITE);
_engine->_menu->drawRectBorders(filledRect);
_engine->drawText(positions.frontTopRightPoint2D.x, positions.frontTopRightPoint2D.y, Common::String::format("ID: %i", i), true, false, boxwidth);
state = true;
}
return state;
}
void DebugState::addFrameData(uint32 frameTime, int32 waitMillis, uint32 maxDelay) {
if (!_frameDataRecording) {
return;
}
_frameData.emplace_back(frameTime, waitMillis, maxDelay);
}
void DebugState::renderDebugView() {
if (_showingZones) {
displayZones();
}
if (_showingActors) {
displayActors();
}
if (_showingTracks) {
displayTracks();
}
}
} // namespace TwinE

View File

@@ -0,0 +1,122 @@
/* 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 TWINE_DEBUG_SCENE_H
#define TWINE_DEBUG_SCENE_H
#include "common/array.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "twine/debugger/ringbuffer.h"
#include "twine/shared.h"
#include <cstdarg>
namespace TwinE {
class TwinEEngine;
class DebugState {
private:
TwinEEngine *_engine;
void projectBoundingBoxPoints(IVec3 *point3d, IVec3 *point3dProjected);
bool checkZoneType(ZoneType type) const;
bool displayZones();
bool displayActors();
bool displayTracks();
struct ScenePositionsProjected {
IVec3 frontBottomLeftPoint;
IVec3 frontBottomRightPoint;
IVec3 frontTopLeftPoint;
IVec3 frontTopRightPoint;
IVec3 backBottomLeftPoint;
IVec3 backBottomRightPoint;
IVec3 backTopLeftPoint;
IVec3 backTopRightPoint;
IVec3 frontBottomLeftPoint2D;
IVec3 frontBottomRightPoint2D;
IVec3 frontTopLeftPoint2D;
IVec3 frontTopRightPoint2D;
IVec3 backBottomLeftPoint2D;
IVec3 backBottomRightPoint2D;
IVec3 backTopLeftPoint2D;
IVec3 backTopRightPoint2D;
};
ScenePositionsProjected calculateBoxPositions(const IVec3 &mins, const IVec3 &maxs);
bool drawBox(const ScenePositionsProjected &positions, uint8 color);
/** Change scenario camera positions */
void changeGridCamera();
public:
DebugState(TwinEEngine *engine);
bool _showingZones = false;
bool _showingActors = false;
bool _showingTracks = false;
bool _showingClips = false;
bool _godMode = false;
unsigned int _typeZones = 127; // all zones on as default
int16 _onlyLoadActor = -1;
const char *_openPopup = nullptr;
bool _holomapFlagsWindow = false;
bool _gameFlagsWindow = false;
bool _menuTextWindow = false;
bool _sceneDetailsWindow = false;
bool _actorDetailsWindow = true;
bool _sceneFlagsWindow = false;
bool _paletteWindow = false;
bool _loggerWindow = false;
bool _frameTimeWindow = false;
bool _frameDataRecording = true;
bool _playFoundItemAnimation = false;
bool _useFreeCamera = false;
bool _disableGridRendering = false;
TextBankId _textBankId = TextBankId::Inventory_Intro_and_Holomap;
void renderDebugView();
void drawClip(const Common::Rect &rect);
struct FrameData {
uint32 frameTime;
int32 waitMillis;
uint32 maxDelay;
};
using FrameDataBuffer = RingBuffer<FrameData, 256>;
FrameDataBuffer _frameData;
void addFrameData(uint32 frameTime, int32 waitMillis, uint32 maxDelay);
void update();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,953 @@
/* 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 "twine/debugger/debugtools.h"
#include "backends/imgui/components/imgui_logger.h"
#include "backends/imgui/imgui.h"
#include "backends/imgui/imgui_fonts.h"
#include "backends/imgui/imgui_utils.h"
#include "common/log.h"
#include "common/scummsys.h"
#include "common/str-enc.h"
#include "common/str.h"
#include "common/util.h"
#include "graphics/palette.h"
#include "twine/debugger/debug_state.h"
#include "twine/debugger/dt-internal.h"
#include "twine/holomap.h"
#include "twine/holomap_v1.h"
#include "twine/parser/entity.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
namespace ImGuiEx {
bool InputIVec3(const char *label, TwinE::IVec3 &v, ImGuiInputTextFlags flags = 0) {
int tmp[3] = {v.x, v.y, v.z};
ImGui::InputInt3(label, tmp, flags);
if (ImGui::IsItemDeactivatedAfterEdit()) {
v.x = tmp[0];
v.y = tmp[1];
v.z = tmp[2];
return true;
}
return false;
}
bool InputAngle(const char *label, int32 *v, int step = 1, int step_fast = 100, const char *format = "%.2f", ImGuiInputTextFlags flags = 0) {
double tmp = TwinE::AngleToDegree(*v);
if (ImGui::InputDouble(label, &tmp, step, step_fast, format, flags)) {
*v = TwinE::DegreeToAngle(tmp);
return true;
}
ImGui::SetItemTooltip("Angle: %i", (int)*v);
return false;
}
bool InputBoundingBox(ImGuiID id, const char *prefixLabel, TwinE::BoundingBox &bbox) {
TwinE::BoundingBox copy = bbox;
Common::String idStr = Common::String::format("%s mins##mins%u", prefixLabel, id);
if (ImGuiEx::InputIVec3(idStr.c_str(), copy.mins)) {
if (copy.isValid()) {
bbox.mins = copy.mins;
}
return true;
}
idStr = Common::String::format("%s maxs##maxs%u", prefixLabel, id);
if (ImGuiEx::InputIVec3(idStr.c_str(), copy.maxs)) {
if (copy.isValid()) {
bbox.maxs = copy.maxs;
}
return true;
}
return false;
}
} // namespace ImGuiEx
namespace TwinE {
#define HOLOMAP_FLAGS_TITLE "Holomap flags"
#define GAME_FLAGS_TITLE "Game flags"
#define ACTOR_DETAILS_TITLE "Actor"
#define MENU_TEXT_TITLE "Menu texts"
static const char *toString(ShapeType type) {
switch (type) {
case ShapeType::kNone:
return "None";
case ShapeType::kSolid:
return "Solid";
case ShapeType::kStairsTopLeft:
return "StairsTopLeft";
case ShapeType::kStairsTopRight:
return "StairsTopRight";
case ShapeType::kStairsBottomLeft:
return "StairsBottomLeft";
case ShapeType::kStairsBottomRight:
return "StairsBottomRight";
case ShapeType::kDoubleSideStairsTop1:
return "DoubleSideStairsTop1";
case ShapeType::kDoubleSideStairsBottom1:
return "DoubleSideStairsBottom1";
case ShapeType::kDoubleSideStairsLeft1:
return "DoubleSideStairsLeft1";
case ShapeType::kDoubleSideStairsRight1:
return "DoubleSideStairsRight1";
case ShapeType::kDoubleSideStairsTop2:
return "DoubleSideStairsTop2";
case ShapeType::kDoubleSideStairsBottom2:
return "DoubleSideStairsBottom2";
case ShapeType::kDoubleSideStairsLeft2:
return "DoubleSideStairsLeft2";
case ShapeType::kDoubleSideStairsRight2:
return "DoubleSideStairsRight2";
case ShapeType::kFlatBottom1:
return "FlatBottom1";
case ShapeType::kFlatBottom2:
return "FlatBottom2";
default:
return "Unknown";
}
}
static void onLog(LogMessageType::Type type, int level, uint32 debugChannel, const char *message) {
switch (type) {
case LogMessageType::kError:
_logger->addLog("[error]%s", message);
break;
case LogMessageType::kWarning:
_logger->addLog("[warn]%s", message);
break;
case LogMessageType::kInfo:
_logger->addLog("%s", message);
break;
case LogMessageType::kDebug:
_logger->addLog("[debug]%s", message);
break;
}
}
void onImGuiInit() {
ImGuiIO &io = ImGui::GetIO();
io.Fonts->AddFontDefault();
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.PixelSnapH = false;
icons_config.OversampleH = 3;
icons_config.OversampleV = 3;
icons_config.GlyphOffset = {0, 4};
static const ImWchar icons_ranges[] = {ICON_MIN_MS, ICON_MAX_MS, 0};
ImGui::addTTFFontFromArchive("MaterialSymbolsSharp.ttf", 16.f, &icons_config, icons_ranges);
_tinyFont = ImGui::addTTFFontFromArchive("LiberationSans-Regular.ttf", 10.0f, nullptr, nullptr);
_logger = new ImGuiEx::ImGuiLogger;
Common::setLogWatcher(onLog);
}
static void holomapFlagsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_holomapFlagsWindow) {
return;
}
if (ImGui::Begin(HOLOMAP_FLAGS_TITLE, &engine->_debugState->_holomapFlagsWindow)) {
if (ImGui::BeginTable("###holomapflags", 8)) {
for (int i = 0; i < engine->numHoloPos(); ++i) {
ImGui::TableNextColumn();
Common::String id = Common::String::format("[%03d]", i);
ImGuiEx::InputInt(id.c_str(), &engine->_gameState->_holomapFlags[i]);
ImGui::SetItemTooltip("%s", engine->_holomap->getLocationName(i));
}
ImGui::EndTable();
}
if (engine->isLBA1()) {
HolomapV1 *holomap = (HolomapV1*)engine->_holomap;
ImGuiEx::InputInt("current", &holomap->_current);
ImGuiEx::InputInt("otimer", &holomap->_otimer);
ImGuiEx::InputInt("dalpha", &holomap->_dalpha);
ImGuiEx::InputInt("dbeta", &holomap->_dbeta);
ImGuiEx::InputInt("calpha", &holomap->_calpha);
ImGuiEx::InputInt("cbeta", &holomap->_cbeta);
ImGuiEx::InputInt("cgamma", &holomap->_cgamma);
ImGuiEx::InputInt("oalpha", &holomap->_oalpha);
ImGuiEx::InputInt("obeta", &holomap->_obeta);
ImGui::Checkbox("automove", &holomap->_automove);
ImGui::Checkbox("flagredraw", &holomap->_flagredraw);
ImGui::Checkbox("dialstat", &holomap->_dialstat);
ImGui::Checkbox("flagpal", &holomap->_flagpal);
}
}
ImGui::End();
}
static void paletteWindow(TwinEEngine *engine) {
if (!engine->_debugState->_paletteWindow) {
return;
}
const ImVec2 available = ImGui::GetContentRegionAvail();
const float contentRegionHeight = available.y + ImGui::GetCursorPosY();
const ImVec2 windowSize(10.0f * ImGui::GetFrameHeight(), contentRegionHeight);
ImGui::SetNextWindowSize(windowSize, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Palettes", &engine->_debugState->_paletteWindow)) {
if (engine->_screens->_flagPalettePcx) {
ImGui::Text("palettepcx is active");
} else {
ImGui::Text("ptrpal is active");
}
ImGui::SeparatorText("Front buffer palette");
const Graphics::Palette &frontBufferPalette = engine->_frontVideoBuffer.getPalette();
ImGui::PushID("frontBufferPalette");
ImGuiEx::Palette(frontBufferPalette);
ImGui::PopID();
ImGui::SeparatorText("PalettePCX");
ImGui::PushID("palettePcx");
ImGuiEx::Palette(engine->_screens->_palettePcx);
ImGui::PopID();
ImGui::SeparatorText("Palette");
ImGui::PushID("ptrPal");
ImGuiEx::Palette(engine->_screens->_ptrPal);
ImGui::PopID();
}
ImGui::End();
}
static float WaitTime(void *data, int i) {
TwinE::DebugState::FrameDataBuffer &buffer = *(TwinE::DebugState::FrameDataBuffer *)data;
return (float)buffer[i].waitMillis;
}
static float FrameTime(void *data, int i) {
TwinE::DebugState::FrameDataBuffer &buffer = *(TwinE::DebugState::FrameDataBuffer *)data;
return (float)buffer[i].frameTime;
}
static void frameTimeWindow(TwinEEngine *engine) {
if (!engine->_debugState->_frameTimeWindow) {
return;
}
if (ImGui::Begin("Frame time", &engine->_debugState->_frameTimeWindow)) {
ImGui::Checkbox("Record", &engine->_debugState->_frameDataRecording);
ImGui::PlotHistogram("Wait time", WaitTime, &engine->_debugState->_frameData, (int)engine->_debugState->_frameData.size(), 0, "Wait time in millis", -100.0f, 100.0f, ImVec2(0, 80));
ImGui::PlotHistogram("Frame time", FrameTime, &engine->_debugState->_frameData, (int)engine->_debugState->_frameData.size(), 0, "Frame time in millis", -100.0f, 100.0f, ImVec2(0, 80));
}
ImGui::End();
}
static void sceneFlagsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_sceneFlagsWindow) {
return;
}
if (ImGui::Begin("Scene flags", &engine->_debugState->_sceneFlagsWindow)) {
if (ImGui::BeginTable("###sceneflags", 8)) {
for (int i = 0; i < NUM_SCENES_FLAGS; ++i) {
ImGui::TableNextColumn();
Common::String id = Common::String::format("[%03d]", i);
ImGuiEx::InputInt(id.c_str(), &engine->_scene->_listFlagCube[i]);
}
ImGui::EndTable();
}
}
ImGui::End();
}
static void gameFlagsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_gameFlagsWindow) {
return;
}
if (ImGui::Begin(GAME_FLAGS_TITLE, &engine->_debugState->_gameFlagsWindow)) {
ImGui::Text("Chapter %i", engine->_gameState->getChapter());
if (ImGui::BeginTable("###gameflags", 8)) {
for (int i = 0; i < NUM_GAME_FLAGS; ++i) {
ImGui::TableNextColumn();
Common::String id = Common::String::format("[%03d]", i);
int16 val = engine->_gameState->hasGameFlag(i);
if (ImGuiEx::InputInt(id.c_str(), &val)) {
engine->_gameState->setGameFlag(i, val);
}
}
ImGui::EndTable();
}
}
ImGui::End();
}
static void menuTextsWindow(TwinEEngine *engine) {
if (!engine->_debugState->_menuTextWindow) {
return;
}
if (ImGui::Begin(MENU_TEXT_TITLE, &engine->_debugState->_menuTextWindow)) {
int id = (int)engine->_debugState->_textBankId;
if (ImGui::InputInt("Text bank", &id)) {
engine->_debugState->_textBankId = (TextBankId)id;
}
const TextBankId oldTextBankId = engine->_text->textBank();
engine->_text->initDial(engine->_debugState->_textBankId);
for (int32 i = 0; i < 1000; ++i) {
char buf[256];
if (engine->_text->getMenuText((TextId)i, buf, sizeof(buf))) {
ImGui::Text("%4i: %s\n", i, buf);
}
}
engine->_text->initDial(oldTextBankId);
}
ImGui::End();
}
static void sceneSelectionCombo(TwinEEngine *engine) {
Scene *scene = engine->_scene;
GameState *gameState = engine->_gameState;
Common::U32String originalSceneName(gameState->_sceneName, Common::kDos850);
const Common::String sceneName = originalSceneName.encode(Common::kUtf8);
if (ImGui::BeginCombo("Scene", sceneName.c_str())) {
for (int i = 0; i < engine->numHoloPos(); ++i) {
Common::U32String originalLocationName(engine->_holomap->getLocationName(i), Common::kDos850);
const Common::String locationName = originalLocationName.encode(Common::kUtf8);
Common::String name = Common::String::format("[%03d] %s", i, locationName.c_str());
if (ImGui::Selectable(name.c_str(), i == engine->_scene->_numCube)) {
scene->_numCube = i;
scene->_newCube = scene->_numCube;
engine->_redraw->_firstTime = true;
}
}
ImGui::EndCombo();
}
}
static const struct ZonesDesc {
const char *name;
ZoneType type;
const char *desc;
} ZoneDescriptions[] = {
{"Cube", ZoneType::kCube, "Change to another scene"},
{"Camera", ZoneType::kCamera, "Binds camera view"},
{"Sceneric", ZoneType::kSceneric, "For use in Life Script"},
{"Grid", ZoneType::kGrid, "Set disappearing Grid fragment"},
{"Object", ZoneType::kObject, "Give bonus"},
{"Text", ZoneType::kText, "Displays text message"},
{"Ladder", ZoneType::kLadder, "Hero can climb on it"},
{"Escalator", ZoneType::kEscalator, nullptr},
{"Hit", ZoneType::kHit, nullptr},
{"Rail", ZoneType::kRail, nullptr}};
static void sceneDetailsWindows(TwinEEngine *engine) {
if (!engine->_debugState->_sceneDetailsWindow) {
return;
}
if (ImGui::Begin("Scene", &engine->_debugState->_sceneDetailsWindow)) {
Scene *scene = engine->_scene;
GameState *gameState = engine->_gameState;
ImGui::Text("Scene: %i", scene->_numCube);
ImGui::Text("Scene name: %s", gameState->_sceneName);
sceneSelectionCombo(engine);
if (ImGui::Checkbox("Bounding boxes", &engine->_debugState->_showingActors)) {
engine->_redraw->_firstTime = true;
}
if (ImGui::Checkbox("Clipping", &engine->_debugState->_showingClips)) {
engine->_redraw->_firstTime = true;
}
if (ImGui::Checkbox("Zones", &engine->_debugState->_showingZones)) {
engine->_redraw->_firstTime = true;
}
// if (ImGui::Checkbox("Tracks", &engine->_debugState->_showingTracks)) {
// engine->_redraw->_firstTime = true;
// }
if (engine->_debugState->_showingZones) {
if (ImGui::CollapsingHeader("Show zone types")) {
for (int i = 0; i < ARRAYSIZE(ZoneDescriptions); ++i) {
if (ImGui::CheckboxFlags(ZoneDescriptions[i].name, &engine->_debugState->_typeZones, (1u << (uint32)ZoneDescriptions[i].type))) {
engine->_redraw->_firstTime = true;
}
if (ZoneDescriptions[i].desc) {
ImGui::SetItemTooltip(ZoneDescriptions[i].desc);
}
}
}
}
if (ImGui::CollapsingHeader("Zones##zonesheader")) {
for (int i = 0; i < scene->_sceneNumZones; ++i) {
ZoneStruct *zone = &scene->_sceneZones[i];
ImGui::Text("Zone idx: %i", i);
ImGui::Indent();
const ZonesDesc &zoneDesc = ZoneDescriptions[(int)zone->type];
ImGui::Text("Type: %s", zoneDesc.name);
if (zoneDesc.desc != nullptr) {
ImGui::SameLine();
ImGui::Text("%s", zoneDesc.desc);
}
ImGui::PushID(i);
ImGuiEx::InputIVec3("Mins", zone->mins);
ImGuiEx::InputIVec3("Maxs", zone->maxs);
ImGui::PopID();
ImGui::Text("Num: %i", zone->num);
ImGui::Text("Info0: %i", zone->infoData.generic.info0);
ImGui::Text("Info1: %i", zone->infoData.generic.info1);
ImGui::Text("Info2: %i", zone->infoData.generic.info2);
ImGui::Text("Info3: %i", zone->infoData.generic.info3);
ImGui::Text("Info4: %i", zone->infoData.generic.info4);
ImGui::Text("Info5: %i", zone->infoData.generic.info5);
ImGui::Text("Info6: %i", zone->infoData.generic.info6);
ImGui::Text("Info7: %i", zone->infoData.generic.info7);
ImGui::Unindent();
}
}
if (ImGui::CollapsingHeader("Tracks##tracksheader")) {
for (int i = 0; i < scene->_sceneNumTracks; ++i) {
ImGui::Text("Track %i: %i %i %i", i, scene->_sceneTracks[i].x, scene->_sceneTracks[i].y, scene->_sceneTracks[i].z);
}
}
if (ImGui::CollapsingHeader("Trajectories##trajectoriesheader")) {
const TrajectoryData &trajectories = engine->_resources->getTrajectories();
for (int i = 0; i < (int)trajectories.getTrajectories().size(); ++i) {
const Trajectory *trajectory = trajectories.getTrajectory(i);
ImGui::Text("Trajectory %i", i);
ImGui::SameLine();
Common::String buttonId = Common::String::format("Activate##activateTrajectory%i", i);
if (ImGui::Button(buttonId.c_str())) {
scene->_numHolomapTraj = i;
scene->reloadCurrentScene();
}
ImGui::Indent();
ImGui::Text("location: %i", trajectory->locationIdx);
ImGui::Text("trajectory location: %i", trajectory->trajLocationIdx);
ImGui::Text("vehicle: %i", trajectory->vehicleIdx);
ImGui::Text("pos: %i %i %i", trajectory->angle.x, trajectory->angle.y, trajectory->angle.z);
ImGui::Text("num anim frames: %i", trajectory->numAnimFrames);
ImGui::Unindent();
}
}
ImGuiEx::InputInt("Previous scene index", &scene->_oldcube);
ImGuiEx::InputInt("Need change scene index", &scene->_newCube);
ImGui::Text("Climbing flag");
ImGui::SameLine();
ImGuiEx::Boolean(scene->_flagClimbing);
ImGuiEx::InputInt("Currently followed actor", &scene->_numObjFollow);
ImGui::Checkbox("Render grid tiles", &scene->_flagRenderGrid);
ImGuiEx::InputInt("Current script value", &scene->_currentScriptValue);
ImGuiEx::InputInt("Talking actor", &scene->_talkingActor);
ImGuiEx::InputInt("Cube jingle", &scene->_cubeJingle);
ImGuiEx::InputIVec3("New hero pos", scene->_sceneStart);
ImGuiEx::InputInt("Alpha light", &scene->_alphaLight);
ImGuiEx::InputInt("Beta light", &scene->_betaLight);
ImGuiEx::InputInt("Fall Y position", &scene->_startYFalling);
ImGui::Text("Hero position type: %i", (int)scene->_flagChgCube);
}
ImGui::End();
}
static void actorDetailsWindow(int &actorIdx, TwinEEngine *engine) {
if (!engine->_debugState->_actorDetailsWindow) {
return;
}
ActorStruct *actor = engine->_scene->getActor(actorIdx);
if (actor == nullptr) {
return;
}
if (ImGui::Begin(ACTOR_DETAILS_TITLE, &engine->_debugState->_actorDetailsWindow)) {
if (actorIdx < 0 || actorIdx > engine->_scene->_nbObjets) {
actorIdx = 0;
}
Common::String currentActorLabel = Common::String::format("Actor %i", actorIdx);
if (ImGui::BeginCombo("Actor", currentActorLabel.c_str())) {
for (int i = 0; i < engine->_scene->_nbObjets; ++i) {
Common::String label = Common::String::format("Actor %i", i);
if (engine->_scene->_mecaPenguinIdx == i) {
label += " (Penguin)";
}
const bool selected = i == actorIdx;
if (ImGui::Selectable(label.c_str(), selected)) {
actorIdx = i;
}
}
ImGui::EndCombo();
}
ImGui::Separator();
ImGuiEx::InputIVec3("Pos", actor->_posObj);
ImGuiEx::InputAngle("Rotation", &actor->_beta);
ImGuiEx::InputInt("Rotation speed", &actor->_srot);
ImGuiEx::InputInt("Life", &actor->_lifePoint);
ImGuiEx::InputInt("Armor", &actor->_armor);
ImGuiEx::InputBoundingBox(actorIdx, "Bounding box", actor->_boundingBox);
if (ImGui::CollapsingHeader("Properties")) {
if (ImGui::BeginTable("Properties", 2)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
ImGui::Text("Followed");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_followedActor);
ImGui::TableNextColumn();
ImGui::Text("Control mode");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_move);
ImGui::TableNextColumn();
ImGui::Text("Delay");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_delayInMillis);
ImGui::TableNextColumn();
ImGui::Text("Strength");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_hitForce);
ImGui::TableNextColumn();
ImGui::Text("Hit by");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_hitBy);
ImGui::TableNextColumn();
ImGui::Text("Bonus");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_bonusParameter);
ImGui::TableNextColumn();
ImGui::Text("ZoneSce");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_zoneSce);
ImGui::TableNextColumn();
ImGui::Text("Brick shape");
ImGui::TableNextColumn();
ImGui::Text("%s", toString(actor->brickShape()));
ImGui::TableNextColumn();
ImGui::Text("Brick causes damage");
ImGui::TableNextColumn();
ImGuiEx::Boolean(actor->brickCausesDamage());
ImGui::TableNextColumn();
ImGui::Text("Collision");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_objCol);
ImGui::TableNextColumn();
ImGui::Text("Carried by");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_carryBy);
ImGui::TableNextColumn();
ImGui::Text("Talk color");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_talkColor);
ImGui::TableNextColumn();
ImGui::Text("Body");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_body); // TODO: link to resources
ImGui::TableNextColumn();
ImGui::Text("Gen body");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_genBody);
ImGui::TableNextColumn();
ImGui::Text("Save gen body");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_saveGenBody);
ImGui::TableNextColumn();
ImGui::Text("Gen anim");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_genAnim);
ImGui::TableNextColumn();
ImGui::Text("Next gen anim");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_nextGenAnim);
ImGui::TableNextColumn();
ImGui::Text("Ptr anim action");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_ptrAnimAction);
ImGui::TableNextColumn();
ImGui::Text("Sprite");
ImGui::TableNextColumn();
ImGui::Text("%i", actor->_sprite);
ImGui::TableNextColumn();
ImGui::Text("A3DS");
ImGui::TableNextColumn();
ImGui::Text("%i %i %i", actor->A3DS.Num, actor->A3DS.Deb, actor->A3DS.Fin);
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("Work Flags")) {
static const char *Names[] = {
"WAIT_HIT_FRAME",
"OK_HIT",
"ANIM_END",
"NEW_FRAME",
"WAS_DRAWN",
"OBJ_DEAD",
"AUTO_STOP_DOOR",
"ANIM_MASTER_ROT",
"FALLING",
"IS_TARGETABLE",
"IS_BLINKING",
"DRAW_SHADOW",
"ANIM_MASTER_GRAVITY",
"SKATING",
"OK_RENVOIE",
"LEFT_JUMP",
"RIGHT_JUMP",
"WAIT_SUPER_HIT",
"TRACK_MASTER_ROT",
"FLY_JETPACK",
"DONT_PICK_CODE_JEU",
"MANUAL_INTER_FRAME",
"WAIT_COORD",
"CHECK_FALLING"};
if (ImGui::BeginTable("##workflags", 6)) {
for (int i = 0; i < ARRAYSIZE(Names); ++i) {
ImGui::TableNextColumn();
ImGui::CheckboxFlags(Names[i], (uint32_t *)&actor->_workFlags, (1 << i));
}
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("Flags")) {
static const char *Names[] = {
"CHECK_OBJ_COL",
"CHECK_BRICK_COL",
"CHECK_ZONE",
"SPRITE_CLIP",
"PUSHABLE",
"COL_BASSE",
"CHECK_CODE_JEU",
"CHECK_WATER_COL",
"0x000100",
"INVISIBLE",
"SPRITE_3D",
"OBJ_FALLABLE",
"NO_SHADOW",
"OBJ_BACKGROUND",
"OBJ_CARRIER",
"MINI_ZV",
"POS_INVALIDE",
"NO_CHOC",
"ANIM_3DS",
"NO_PRE_CLIP",
"OBJ_ZBUFFER",
"OBJ_IN_WATER",
};
if (ImGui::BeginTable("##staticflags", 6)) {
for (int i = 0; i < ARRAYSIZE(Names); ++i) {
ImGui::TableNextColumn();
ImGui::CheckboxFlags(Names[i], (uint32_t *)&actor->_flags, (1 << i));
}
ImGui::EndTable();
}
}
if (actor->_body != -1) {
ImGui::SeparatorText("Body");
if (actor->_entityDataPtr != nullptr) {
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
ImGuiEx::InputBoundingBox((int)(uintptr)&bodyData, "Bounding box", bodyData.bbox);
} else {
ImGui::Text("No entity data");
}
}
ImGui::SeparatorText("Entity");
EntityData &entityData = actor->_entityData;
Common::Array<EntityBody> &entityBodies = entityData.getBodies();
ImGui::Text("Bodies: %i", (int)entityBodies.size());
for (EntityBody &entityBody : entityBodies) {
ImGui::Text("%s index: %i", Resources::HQR_FILE3D_FILE, entityBody.index);
ImGui::Indent();
ImGui::Text("%s index: %i", Resources::HQR_BODY_FILE, entityBody.hqrBodyIndex);
Common::String id = Common::String::format("Has bounding box##%i", entityBody.index);
ImGui::Checkbox(id.c_str(), &entityBody.actorBoundingBox.hasBoundingBox);
ImGuiEx::InputBoundingBox((int)(uintptr)&entityBody, "Bounding box", entityBody.actorBoundingBox.bbox);
ImGui::Unindent();
}
Common::Array<EntityAnim> &animations = entityData.getAnimations();
ImGui::Text("Animations: %i", (int)animations.size());
for (EntityAnim &animation : animations) {
ImGui::Text("Animation type: %i", (int)animation.animation);
ImGui::Indent();
ImGui::Text("Body animation index: %i", (int)animation.animIndex);
ImGui::Text("actions: %i", (int)animation._actions.size());
for (EntityAnim::Action &action : animation._actions) {
ImGui::BulletText("%i", (int)action.type);
}
ImGui::Unindent();
}
}
ImGui::End();
}
static void gameStateMenu(TwinEEngine *engine) {
if (ImGui::BeginMenu("Game State")) {
int keys = engine->_gameState->_nbLittleKeys;
if (ImGui::InputInt("Keys", &keys)) {
engine->_gameState->setKeys(keys);
}
int kashes = engine->_gameState->_goldPieces;
if (ImGui::InputInt("Cash", &kashes)) {
engine->_gameState->setKashes(kashes);
}
int zlitos = engine->_gameState->_zlitosPieces;
if (ImGui::InputInt("Zlitos", &zlitos)) {
engine->_gameState->setZlitos(zlitos);
}
int magicPoints = engine->_gameState->_magicPoint;
if (ImGui::InputInt("Magic points", &magicPoints)) {
engine->_gameState->setMagicPoints(magicPoints);
}
int magicLevel = engine->_gameState->_magicLevelIdx;
if (ImGui::InputInt("Magic level", &magicLevel)) {
engine->_gameState->_magicLevelIdx = CLIP<int16>(magicLevel, 0, 4);
}
int leafs = engine->_gameState->_inventoryNumLeafs;
if (ImGui::InputInt("Leafs", &leafs)) {
engine->_gameState->setLeafs(leafs);
}
int leafBoxes = engine->_gameState->_inventoryNumLeafsBox;
if (ImGui::InputInt("Leaf boxes", &leafBoxes)) {
engine->_gameState->setLeafBoxes(leafBoxes);
}
int gas = engine->_gameState->_inventoryNumGas;
if (ImGui::InputInt("Gas", &gas)) {
engine->_gameState->setGas(gas);
}
const TextBankId oldTextBankId = engine->_text->textBank();
engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);
for (int i = 0; i < NUM_INVENTORY_ITEMS; ++i) {
Common::String label;
if (engine->_text->getText((TextId)(100 + i))) {
Common::U32String original(engine->_text->_currDialTextEntry->string, Common::kDos850);
label = original.encode(Common::kUtf8).substr(0, 30);
} else {
label = Common::String::format("Item %i", i);
}
uint8 &value = engine->_gameState->_inventoryFlags[i];
bool hasItem = value != 0;
if (ImGui::Checkbox(label.c_str(), &hasItem)) {
value = hasItem == 0 ? 0 : 1;
}
}
engine->_text->initDial(oldTextBankId);
ImGui::EndMenu();
}
}
static void gridMenu(TwinEEngine *engine) {
if (ImGui::BeginMenu("Grid")) {
ImGui::Text("World cube %i %i %i", engine->_grid->_worldCube.x, engine->_grid->_worldCube.y, engine->_grid->_worldCube.z);
#if 0
Grid *grid = engine->_grid;
if (ImGui::Button(ICON_MS_ADD)) {
grid->_cellingGridIdx++;
if (grid->_cellingGridIdx > 133) {
grid->_cellingGridIdx = 133;
}
}
if (ImGui::Button(ICON_MS_REMOVE)) {
grid->_cellingGridIdx--;
if (grid->_cellingGridIdx < 0) {
grid->_cellingGridIdx = 0;
}
}
// Enable/disable celling grid
if (ImGui::Button("Apply ceiling grid")) {
if (grid->_useCellingGrid == -1) {
grid->_useCellingGrid = 1;
// grid->createGridMap();
grid->initCellingGrid(grid->_cellingGridIdx);
debug("Enable Celling Grid index: %d", grid->_cellingGridIdx);
engine->_scene->_needChangeScene = SCENE_CEILING_GRID_FADE_2; // tricky to make the fade
} else if (grid->_useCellingGrid == 1) {
grid->_useCellingGrid = -1;
grid->copyMapToCube();
engine->_redraw->_firstTime = true;
debug("Disable Celling Grid index: %d", grid->_cellingGridIdx);
engine->_scene->_needChangeScene = SCENE_CEILING_GRID_FADE_2; // tricky to make the fade
}
}
#endif
ImGui::EndMenu();
}
}
static void debuggerMenu(TwinEEngine *engine) {
if (ImGui::BeginMenu("Debugger")) {
ImGui::Text("Timer: %i", (int)engine->timerRef);
if (ImGui::MenuItem("Logs")) {
engine->_debugState->_loggerWindow = true;
}
if (ImGui::MenuItem("Texts")) {
engine->_debugState->_menuTextWindow = true;
}
if (ImGui::MenuItem("Holomap flags")) {
engine->_debugState->_holomapFlagsWindow = true;
}
if (ImGui::MenuItem("Game flags")) {
engine->_debugState->_gameFlagsWindow = true;
}
if (ImGui::MenuItem("Scene details")) {
engine->_debugState->_sceneDetailsWindow = true;
}
if (ImGui::MenuItem("Scene flags")) {
engine->_debugState->_sceneFlagsWindow = true;
}
if (ImGui::MenuItem("Actor details")) {
engine->_debugState->_actorDetailsWindow = true;
}
if (ImGui::MenuItem("Frame time")) {
engine->_debugState->_frameTimeWindow = true;
}
ImGui::SeparatorText("Actions");
if (ImGui::MenuItem("Center actor")) {
ActorStruct *actor = engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
actor->_posObj = engine->_grid->_worldCube;
actor->_posObj.y += 16 * SIZE_BRICK_Y;
int32 y = actor->_posObj.y - 1 - SIZE_BRICK_Y;
while (y > 0 && ShapeType::kNone == engine->_grid->worldColBrick(actor->_posObj.x, y, actor->_posObj.z)) {
y -= SIZE_BRICK_Y;
}
actor->_posObj.y = (y + SIZE_BRICK_Y) & ~(SIZE_BRICK_Y - 1);
}
if (ImGui::BeginMenu("Animations")) {
if (ImGui::MenuItem("Found item")) {
engine->_debugState->_playFoundItemAnimation = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Palettes")) {
LifeScriptContext fakeCtx(0, engine->_scene->_sceneHero);
if (ImGui::MenuItem("Show palette")) {
engine->_debugState->_paletteWindow = true;
}
if (ImGui::MenuItem("Dark palette")) {
engine->_scriptLife->lSET_DARK_PAL(engine, fakeCtx);
}
if (ImGui::MenuItem("Normal palette")) {
engine->_scriptLife->lSET_NORMAL_PAL(engine, fakeCtx);
}
#if 0
// TODO: the fade functions are blocking and break the imgui begin/end cycle
if (ImGui::MenuItem("lFADE_PAL_RED")) {
engine->_scriptLife->lFADE_PAL_RED(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_ALARM_RED")) {
engine->_scriptLife->lFADE_ALARM_RED(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_ALARM_PAL")) {
engine->_scriptLife->lFADE_ALARM_PAL(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_RED_PAL")) {
engine->_scriptLife->lFADE_RED_PAL(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_RED_ALARM")) {
engine->_scriptLife->lFADE_RED_ALARM(engine, fakeCtx);
}
if (ImGui::MenuItem("lFADE_PAL_ALARM")) {
engine->_scriptLife->lFADE_PAL_ALARM(engine, fakeCtx);
}
#endif
ImGui::EndMenu();
}
if (ImGui::MenuItem("Force Redraw")) {
engine->_redraw->_firstTime = true;
}
ImGui::SeparatorText("Options");
ImGui::Checkbox("Free camera", &engine->_debugState->_useFreeCamera);
ImGui::Checkbox("God mode", &engine->_debugState->_godMode);
sceneSelectionCombo(engine);
ImGui::EndMenu();
}
}
void onImGuiRender() {
if (!debugChannelSet(-1, kDebugImGui)) {
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse;
return;
}
static int currentActor = 0;
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
TwinEEngine *engine = (TwinEEngine *)g_engine;
if (ImGui::BeginMainMenuBar()) {
debuggerMenu(engine);
gameStateMenu(engine);
gridMenu(engine);
ImGui::EndMainMenuBar();
}
actorDetailsWindow(currentActor, engine);
sceneDetailsWindows(engine);
menuTextsWindow(engine);
holomapFlagsWindow(engine);
gameFlagsWindow(engine);
paletteWindow(engine);
sceneFlagsWindow(engine);
frameTimeWindow(engine);
_logger->draw("Logger", &engine->_debugState->_loggerWindow);
if (engine->_debugState->_openPopup) {
ImGui::OpenPopup(engine->_debugState->_openPopup);
engine->_debugState->_openPopup = nullptr;
}
}
void onImGuiCleanup() {
Common::setLogWatcher(nullptr);
delete _logger;
_logger = nullptr;
}
} // namespace TwinE

View File

@@ -0,0 +1,31 @@
/* 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 TWINE_DEBUGGER_DEBUGTOOLS_H
#define TWINE_DEBUGGER_DEBUGTOOLS_H
namespace TwinE {
void onImGuiInit();
void onImGuiRender();
void onImGuiCleanup();
} // namespace TwinE
#endif

View File

@@ -0,0 +1,34 @@
/* 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 TWINE_DEBUGGER_DT_INTERNAL_H
#define TWINE_DEBUGGER_DT_INTERNAL_H
#include "backends/imgui/components/imgui_logger.h"
namespace TwinE {
ImFont *_tinyFont = nullptr;
ImGuiEx::ImGuiLogger *_logger = nullptr;
} // namespace TwinE
#endif

View File

@@ -0,0 +1,215 @@
/**
* @file
*/
#pragma once
#include "common/util.h"
#include <stddef.h>
namespace TwinE {
/**
* @brief Non allocating buffer class holds a maximum of given entries and allows an endless insert
* by overrinding previous elements in the buffer
*/
template<typename TYPE, size_t SIZE = 64u>
class RingBuffer {
protected:
size_t _size;
size_t _front;
size_t _back;
TYPE _buffer[SIZE];
public:
using value_type = TYPE;
RingBuffer() : _size(0u), _front(0u), _back(SIZE - 1u) {
}
class iterator {
private:
RingBuffer *_ringBuffer;
size_t _idx;
public:
iterator(RingBuffer *ringBuffer, size_t idx) : _ringBuffer(ringBuffer), _idx(idx) {
}
inline const TYPE &operator*() const {
return _ringBuffer->_buffer[_idx % _ringBuffer->capacity()];
}
inline TYPE &operator*() {
return _ringBuffer->_buffer[_idx % _ringBuffer->capacity()];
}
inline const TYPE &operator()() const {
return _ringBuffer->_buffer[_idx % _ringBuffer->capacity()];
}
inline TYPE &operator()() {
return _ringBuffer->_buffer[_idx % _ringBuffer->capacity()];
}
iterator &operator++() {
++_idx;
return *this;
}
inline bool operator!=(const iterator &rhs) const {
return _ringBuffer != rhs._ringBuffer || _idx != rhs._idx;
}
inline bool operator==(const iterator &rhs) const {
return _ringBuffer == rhs._ringBuffer && _idx == rhs._idx;
}
inline const TYPE *operator->() const {
return &_ringBuffer->_buffer[_idx % _ringBuffer->capacity()];
}
};
iterator begin() {
return iterator(this, _front);
}
iterator end() {
return iterator(this, _front + _size);
}
iterator begin() const {
return iterator(const_cast<RingBuffer *>(this), _front);
}
iterator end() const {
return iterator(const_cast<RingBuffer *>(this), _front + _size);
}
inline size_t size() const {
return _size;
}
constexpr size_t capacity() const {
return SIZE;
}
inline bool empty() const {
return _size == 0;
}
/**
* @brief Access to the first element in the buffer
* @note This is not the same as accessing the index 0 element
*/
const TYPE &front() const {
return _buffer[_front];
}
/**
* @brief Access to the first element in the buffer
* @note This is not the same as accessing the index 0 element
*/
TYPE &front() {
return _buffer[_front];
}
/**
* @brief Access to the last element in the buffer
* @note This is not the same as accessing the index @c size-1 element
*/
const TYPE &back() const {
return _buffer[_back];
}
/**
* @brief Access to the last element in the buffer
* @note This is not the same as accessing the index @c size-1 element
*/
TYPE &back() {
return _buffer[_back];
}
/**
* @brief Clears the whole ring buffer
* @note Does not call any destructors - they are called when the buffer itself gets destroyed
*/
void clear() {
_front = 0u;
_back = SIZE - 1u;
_size = 0u;
}
/**
* @brief Pushes an element to the end of the buffer
*/
void push_back(const TYPE &x) {
_back = (_back + 1u) % SIZE;
if (_size == SIZE) {
_front = (_front + 1u) % SIZE;
} else {
++_size;
}
_buffer[_back] = x;
}
/**
* @brief Pushes an elements to the end of the buffer
* @note Performs a move operation
*/
template<typename... _Args>
void emplace_back(_Args &&...args) {
_back = (_back + 1u) % SIZE;
if (_size == SIZE) {
_front = (_front + 1u) % SIZE;
} else {
++_size;
}
_buffer[_back] = Common::move(TYPE{Common::forward<_Args>(args)...});
}
/**
* @brief Removes element from the beginning of the buffer
*/
void pop() {
if (_size == 0) {
return;
}
--_size;
_front = (_front + 1u) % SIZE;
}
/**
* @brief Erase the given amount of elements from the end of the buffer
*/
void erase_back(const size_t n) {
if (n >= _size) {
clear();
return;
}
_size -= n;
_back = (_front + _size - 1u) % SIZE;
}
/**
* @brief Erase the given amount of elements from the beginning of the buffer
*/
void erase_front(const size_t n) {
if (n >= _size) {
clear();
return;
}
_front = (_front + n) % SIZE;
_size -= n;
}
inline TYPE &operator[](size_t i) {
return _buffer[(_front + i) % SIZE];
}
inline const TYPE &operator[](size_t i) const {
return _buffer[(_front + i) % SIZE];
}
};
} // namespace TwinE

460
engines/twine/detection.cpp Normal file
View File

@@ -0,0 +1,460 @@
/* 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/language.h"
#include "common/translation.h"
#include "engines/advancedDetector.h"
#include "base/plugins.h"
#include "twine/detection.h"
#include "twine/shared.h"
static const PlainGameDescriptor twineGames[] = {
{ "lba", "Little Big Adventure" },
{ "lbashow", "Little Big Adventure Freeware Slide Show" },
{ "lba2", "Little Big Adventure 2" },
{ nullptr, nullptr }
};
static const DebugChannelDef debugFlagList[] = {
{TwinE::kDebugScriptsMove, "scriptsmove", "Move script debugging"},
{TwinE::kDebugScriptsLife, "scriptslife", "Life script debugging"},
{TwinE::kDebugResources, "resources", "Resources debugging"},
{TwinE::kDebugTimers, "timers", "Timer debugging"},
{TwinE::kDebugImGui, "imgui", "UI for debugging"},
{TwinE::kDebugInput, "input", "Input debugging"},
{TwinE::kDebugMovies, "movies", "Movies debugging"},
{TwinE::kDebugPalette, "palette", "Palette debugging"},
{TwinE::kDebugCollision, "collision", "Collision debugging"},
{TwinE::kDebugAnimation, "animation", "Animation debugging"},
{TwinE::kDebugHolomap, "holomap", "Holomap debugging"},
DEBUG_CHANNEL_END
};
#define TWINE_DETECTION_ENTRY(gameid, extra, filesDescriptions, platform, flags) \
{ \
gameid, \
extra, \
filesDescriptions, \
Common::EN_ANY, \
platform, \
flags, \
GUIO1(GUIO_NONE) \
}, \
{ \
gameid, \
extra, \
filesDescriptions, \
Common::FR_FRA, \
platform, \
flags, \
GUIO1(GUIO_NONE) \
}, \
{ \
gameid, \
extra, \
filesDescriptions, \
Common::DE_DEU, \
platform, \
flags, \
GUIO1(GUIO_NONE) \
}, \
{ \
gameid, \
extra, \
filesDescriptions, \
Common::IT_ITA, \
platform, \
flags, \
GUIO1(GUIO_NONE) \
}, \
{ \
gameid, \
extra, \
filesDescriptions, \
Common::ES_ESP, \
platform, \
flags, \
GUIO1(GUIO_NONE) \
}
static const ADGameDescription twineGameDescriptions[] = {
// Little Big Adventure - Preview Version (EN, FR)
// LBA.EXE
// 8 August 1994 at 19:30
{
"lba",
"Preview Version",
AD_ENTRY1s("LBA.EXE", "c1a887e38283d43f271249ad9f2a73ef", 294025),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS | TwinE::TF_PREVIEW,
GUIO1(GUIO_NONE)
},
{
"lba",
"Preview Version",
AD_ENTRY1s("LBA.EXE", "c1a887e38283d43f271249ad9f2a73ef", 294025),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_NO_FLAGS | TwinE::TF_PREVIEW,
GUIO1(GUIO_NONE)
},
// Little Big Adventure - Preview Version (EN, FR)
// LBA.EXE
// 15 August 1994 at 18:28
{
"lba",
"Preview Version",
AD_ENTRY1s("LBA.EXE", "c1a887e38283d43f271249ad9f2a73ef", 298697),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSTABLE | TwinE::TF_PREVIEW,
GUIO1(GUIO_NONE)
},
{
"lba",
"Preview Version",
AD_ENTRY1s("LBA.EXE", "c1a887e38283d43f271249ad9f2a73ef", 298697),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_UNSTABLE | TwinE::TF_PREVIEW,
GUIO1(GUIO_NONE)
},
// Little Big Adventure - Demo Version (EN, FR, DE, IT, ES)
// RELENT.EXE
// 14 October 1994 at 10:18
TWINE_DETECTION_ENTRY("lba", "Demo Version", AD_ENTRY1s("RELENT.EXE", "c1a887e38283d43f271249ad9f2a73ef", 245961), Common::kPlatformDOS, ADGF_DEMO),
// Little Big Adventure - Original European Version (EN, FR, DE, IT, ES)
// LBA.EXE
// 14 Oct 1994 at 12:45
TWINE_DETECTION_ENTRY("lba", "CD Original European Version", AD_ENTRY1s("LBA.EXE", "c1a887e38283d43f271249ad9f2a73ef", 258513), Common::kPlatformDOS, ADGF_CD),
// Relentless: Twinsen's Adventure - Original North America Version (EN, FR, DE, IT, ES)
// RELENT.EXE
// 14 Oct 1994 at 13:22
TWINE_DETECTION_ENTRY("lba", "Relentless: Twinsen's Adventure - CD Original North America Version", AD_ENTRY1s("RELENT.EXE", "c1a887e38283d43f271249ad9f2a73ef", 258513), Common::kPlatformDOS, TwinE::TF_VERSION_USA | ADGF_CD),
// Relentless: Little Big Adventure (Europe) (EN, FR, DE, IT, ES)
// text.hqr
// 22 February 1997 at 14:20
TWINE_DETECTION_ENTRY("lba", "", AD_ENTRY1s("text.hqr", "c30df57432c324a5f4673e013f88acf9", 258513), Common::kPlatformUnknown, 0),
// Little Big Adventure - Demo Version (FR)
// LBA.EXE
// 21 October 1994 at 15:25
{
"lba",
"Demo Version",
AD_ENTRY1s("LBA.EXE", "c1a887e38283d43f271249ad9f2a73ef", 273281),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_DEMO,
GUIO1(GUIO_NONE)
},
// Little Big Adventure - Floppy Disk Version
// FLA_GIF.HQR
// 11 August 1995 at 23:28
{
"lba",
#ifdef USE_GIF
"Floppy Disk Version",
AD_ENTRY1s("FLA_GIF.HQR", "3f7383f65afa212e3eec430627828b64", 1784466),
Common::EN_ANY,
Common::kPlatformDOS,
TwinE::TF_USE_GIF,
#else
_s("This version requires Giflib which was not compiled into ScummVM"),
AD_ENTRY1s("FLA_GIF.HQR", "3f7383f65afa212e3eec430627828b64", 1784466),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSUPPORTED,
#endif
GUIO1(GUIO_NONE)
},
// Little Big Adventure - Original Japanese Version
// LBAJ.EXE
// 15 Oct 1995 at 13:28
{
"lba",
"Original Japanese Version",
AD_ENTRY1s("LBAJ.EXE", "54a1e8749448e08086a1929510ec4b6a", 278043),
Common::JA_JPN,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Virgin Asia cd release - english only
{
"lba",
"Virgin Asia CD release",
AD_ENTRY1s("text.hqr", "5b8329ebd078adc92979d04987692e9b", 442921),
Common::EN_GRB,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Little Big Adventure - DotEmu Version (Steam)
// LBA.DOT
// 11 October 2011 at 17:30
TWINE_DETECTION_ENTRY("lba", "DotEmu Version (Steam)", AD_ENTRY1s("LBA.DOT", "6dc00342c80bc41b4ff5a43c560c7abc", 380666496), Common::kPlatformDOS, ADGF_NO_FLAGS),
// Little Big Adventure (CD Spanish)
TWINE_DETECTION_ENTRY("lba", "", AD_ENTRY1s("text.hqr", "ae7343552f8fbd17a1fc6cea2197a912", 248654), Common::kPlatformDOS, ADGF_NO_FLAGS),
// Little Big Adventure - DotEmu Enhanced Version (Steam)
// LBA.exe
// 27 February 2018 at 08:10
TWINE_DETECTION_ENTRY("lba", "DotEmu Enhanced Version (Steam)", AD_ENTRY1s("LBA.exe", "1f176b4329fbc7efc8f9f30f97013c5f", 1165728), Common::kPlatformWindows, TwinE::TF_DOTEMU_ENHANCED),
// Little Big Adventure - DotEmu Enhanced Version (Android)
// liblba.so
// 8 Sep 2014 at 15:56
TWINE_DETECTION_ENTRY("lba", "DotEmu", AD_ENTRY1s("text.hqr", "a374c93450dd2bb874b7167a63974e8d", 377224), Common::kPlatformAndroid, TwinE::TF_DOTEMU_ENHANCED),
// Twinsen's Little Big Adventure Classic - 2015 Edition (Steam)
TWINE_DETECTION_ENTRY("lba", "DotEmu (Steam)", AD_ENTRY1s("LBA.EXE", "615a9a0c3dae2c3b5fca0dee4d84dc72", 931328), Common::kPlatformWindows, TwinE::TF_DOTEMU_ENHANCED),
// Little Big Adventure - GOG Version
// LBA.GOG
// 11 October 2011 at 17:30
TWINE_DETECTION_ENTRY("lba", "GOG.com Version", AD_ENTRY1s("LBA.GOG", "6dc00342c80bc41b4ff5a43c560c7abc", 380666496), Common::kPlatformDOS, ADGF_NO_FLAGS),
{
"lba",
"",
AD_ENTRY1s("text.hqr", "31d880f658cc6cc6d6cf70df732aec4f", 248829),
Common::EN_GRB,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
{
"lba",
"",
AD_ENTRY1s("text.hqr", "31d880f658cc6cc6d6cf70df732aec4f", 248829),
Common::FR_FRA,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
{
"lba",
"",
AD_ENTRY1s("text.hqr", "31d880f658cc6cc6d6cf70df732aec4f", 248829),
Common::DE_DEU,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Little Big Adventure - Steam Version
// TLBA1C.exe
// 31 May 2022 12:58
TWINE_DETECTION_ENTRY("lba", "Classic Version (Steam)", AD_ENTRY1s("TLBA1C.exe", "38b038eee2d93a5bc0e0405886161252", 4417024), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// Little Big Adventure - Steam Version
// TLBA1C.exe
// 10 Jun 2022 14:51
TWINE_DETECTION_ENTRY("lba", "Classic Version (Steam)", AD_ENTRY1s("TLBA1C.exe", "e4bab4647eabb998f627ac7628d94790", 4418048), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// Little Big Adventure - Steam Version (3.2.2)
// TLBA1C.exe
// 1 Jul 2022
TWINE_DETECTION_ENTRY("lba", "Classic Version (Steam)", AD_ENTRY1s("TLBA1C.exe", "58a7fba8a556196bb14d4f492017fb2b", 4416000), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// Little Big Adventure - Steam Version (3.2.3)
// TLBA1C.exe
// 8 Sep 2022
TWINE_DETECTION_ENTRY("lba", "Classic Version (Steam)", AD_ENTRY1s("TLBA1C.exe", "01f38555eca4a5dd076a4599359de4eb", 4445696), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// Little Big Adventure - GOG Version 3.2.0 (56122)
// TLBA1C.exe
// 1st June 2022 02:18
TWINE_DETECTION_ENTRY("lba", "GOG.com Classic Version", AD_ENTRY1s("TLBA1C.exe", "e377d036e997acbf543bc3023ce72be6", 4404224), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// Little Big Adventure - GOG Version 3.2.1 (56604)
// TLBA1C.exe
// 17 Jun 2022 00:30
TWINE_DETECTION_ENTRY("lba", "GOG.com Classic Version", AD_ENTRY1s("TLBA1C.exe", "e86192e4a491805dc011dda5ca83c608", 4404736), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// Little Big Adventure - GOG Version 3.2.2
// TLBA1C.exe
// 1 Jul 2022
TWINE_DETECTION_ENTRY("lba", "GOG.com Classic Version", AD_ENTRY1s("TLBA1C.exe", "76b227e87038c17a3376b1c681f15474", 4402688), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// Little Big Adventure - GOG Version 3.2.3.1 (58613)
// TLBA1C.exe
TWINE_DETECTION_ENTRY("lba", "GOG.com Classic Version", AD_ENTRY1s("TLBA1C.exe", "252216e83f83dc770fafc7fd787a6da5", 4432896), Common::kPlatformWindows, TwinE::TF_LBA1_CLASSIC),
// FAN Translations - http://lba.fishos.net/bit/index.html
// Portuguese by xesf (alexfont)
{
"lba",
"Fan Translation by xesf",
AD_ENTRY1s("TEXT.HQR", "2a8df71946aa9ee4c777a9d6414b89ce", 282308),
Common::PT_PRT,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Polish by Zink
{
"lba",
"Fan Translation by Zink",
AD_ENTRY1s("text.hqr", "7f41b5e8efb07dd413f59377e03b1b04", 413920),
Common::PL_POL,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Hungarian by Gregorius
{
"lba",
"Fan Translation by Gregorius",
AD_ENTRY1s("TEXT.HQR", "31d760b41a424ec2926f494d7ecac14a", 410709),
Common::HU_HUN,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Hebrew by ChaosFish
{
"lba",
"Fan Translation by ChaosFish",
AD_ENTRY1s("TEXT.HQR", "c1adf48ea71fead82d91c5b062eeeb99", 75866),
Common::HE_ISR,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Brazilian Portuguese by spider_ruler33
{
"lba",
"Fan Translation by spider_ruler33",
AD_ENTRY1s("TEXT.HQR", "2bf227f9e8fcdc7397372b68786c446e", 283631),
Common::PT_BRA,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Russian by Cody
{
"lba",
"Fan Translation by Cody",
AD_ENTRY1s("TEXT.HQR", "93b1a29711f0750156280012e53fdcd2", 280306),
Common::RU_RUS,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// https://forum.magicball.net/showthread.php?p=386653#post386653
// LBA:LID Demo v1.0
{
"lba",
"LBA: Lupin Island Destiny",
AD_ENTRY1s("TEXT.HQR", "859339686e87f5c9f71253c25610a9fd", 240238),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSTABLE | ADGF_DEMO | TwinE::TF_MOD,
GUIO1(GUIO_NONE)
},
// https://forum.magicball.net/showthread.php?t=16549
// LBA:Prequel Demo v1.0
{
"lba",
"LBA: Prequel",
AD_ENTRY1s("TEXT.HQR", "79352a6f59ff2d8984573bfa421ef346", 259722),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_UNSTABLE | ADGF_DEMO | TwinE::TF_MOD,
GUIO1(GUIO_NONE)
},
// LBA Freeware Slide Show
// 4 Apr 1994
{
"lbashow",
"LBA Freeware Slide Show",
AD_ENTRY1s("LBA_SHOW.EXE", "c1a887e38283d43f271249ad9f2a73ef", 85928),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO1(GUIO_NONE)
},
// Little Big Adventure 2
// Little Big Adventure 2 - Original European Version (EN, FR, DE, IT, ES)
// LBA2.EXE
// 4 Sep 2004 at 18:44
TWINE_DETECTION_ENTRY("lba2", "CD Original European Version", AD_ENTRY1s("LBA2.EXE", "ba915d65b3c7a743a87804f73f29675b", 616448), Common::kPlatformDOS, ADGF_UNSTABLE),
TWINE_DETECTION_ENTRY("lba2", "CD Original European Version", AD_ENTRY1s("text.hqr", "dafcec7d9f1b2ab4a12d478786eb0f61", 443010), Common::kPlatformDOS, ADGF_UNSTABLE),
// Little Big Adventure 2 - Demo 1996
// Do 05 Sep 1996 12:13:28 CEST
TWINE_DETECTION_ENTRY("lba2", "Demo Version", AD_ENTRY1s("text.hqr", "227c6da541e310531dd3f86763360dda", 29826), Common::kPlatformDOS, ADGF_UNSUPPORTED | ADGF_DEMO),
// Little Big Adventure 2 - Playable Demo 1997
// June 2nd, 1997
TWINE_DETECTION_ENTRY("lba2", "Playable Demo Version", AD_ENTRY1s("text.hqr", "63bfc42fb50a041f953db923b9397d00", 231241), Common::kPlatformDOS, ADGF_UNSUPPORTED | ADGF_DEMO),
AD_TABLE_END_MARKER
};
class TwinEMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
public:
TwinEMetaEngineDetection() : AdvancedMetaEngineDetection(twineGameDescriptions, twineGames) {
_guiOptions = GUIO11(GAMEOPTION_WALL_COLLISION, GAMEOPTION_DISABLE_SAVE_MENU, GAMEOPTION_DEBUG, GAMEOPTION_SOUND, GAMEOPTION_VOICES, GAMEOPTION_TEXT, GAMEOPTION_MOVIES, GAMEOPTION_MOUSE, GAMEOPTION_USA_VERSION, GAMEOPTION_HIGH_RESOLUTION, GAMEOPTION_TEXT_TO_SPEECH);
}
const char *getName() const override {
return "twine";
}
const char *getEngineName() const override {
return "Little Big Adventure";
}
const char *getOriginalCopyright() const override {
return "Little Big Adventure (C) Adeline Software International";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
REGISTER_PLUGIN_STATIC(TWINE_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, TwinEMetaEngineDetection);

59
engines/twine/detection.h Normal file
View File

@@ -0,0 +1,59 @@
/* 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 TWINE_DETECTION_H
#define TWINE_DETECTION_H
namespace TwinE {
enum TwineGameType {
GType_LBA = 1,
GType_LBA2 = 2,
// slideshow demo of lba1
GType_LBASHOW = 3
};
enum TwineFeatureFlags {
TF_VERSION_EUROPE = (1 << 0),
TF_VERSION_USA = (1 << 1),
TF_VERSION_CUSTOM = (1 << 2),
TF_USE_GIF = (1 << 3),
TF_DOTEMU_ENHANCED = (1 << 4),
TF_LBA1_CLASSIC = (1 << 5),
TF_MOD = (1 << 6),
TF_PREVIEW = (1 << 7)
};
#define GAMEOPTION_WALL_COLLISION GUIO_GAMEOPTIONS1
#define GAMEOPTION_DISABLE_SAVE_MENU GUIO_GAMEOPTIONS2
#define GAMEOPTION_DEBUG GUIO_GAMEOPTIONS3
#define GAMEOPTION_SOUND GUIO_GAMEOPTIONS4
#define GAMEOPTION_VOICES GUIO_GAMEOPTIONS5
#define GAMEOPTION_TEXT GUIO_GAMEOPTIONS6
#define GAMEOPTION_MOVIES GUIO_GAMEOPTIONS7
#define GAMEOPTION_MOUSE GUIO_GAMEOPTIONS8
#define GAMEOPTION_USA_VERSION GUIO_GAMEOPTIONS9
#define GAMEOPTION_HIGH_RESOLUTION GUIO_GAMEOPTIONS10
#define GAMEOPTION_TEXT_TO_SPEECH GUIO_GAMEOPTIONS11
} // End of namespace TwinE
#endif // TWINE_DETECTION_H

79
engines/twine/holomap.h Normal file
View 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/>.
*
*/
#ifndef TWINE_HOLOMAP_H
#define TWINE_HOLOMAP_H
#include "common/scummsys.h"
namespace Common {
class SeekableReadStream;
}
namespace TwinE {
class TwinEEngine;
class BodyData;
class AnimData;
struct RealValue;
struct Vertex;
struct AnimTimerDataStruct;
/**
* The Holomap shows the hero position. The arrows (@c RESSHQR_HOLOARROWMDL) represent important places in your quest - they automatically disappear once that part of
* the quest is done (@c clrHoloPos()). You can rotate the holoamp by pressing ctrl+cursor keys - but only using the cursor keys, you can scroll through the
* text for the visible arrows.
*/
class Holomap {
protected:
TwinEEngine *_engine;
public:
Holomap(TwinEEngine *engine) : _engine(engine) {}
virtual ~Holomap() {}
/**
* Set Holomap location position
* @param locationIdx Scene where position must be set
*/
virtual bool setHoloPos(int32 locationIdx) = 0;
virtual bool loadLocations() = 0;
virtual const char *getLocationName(int index) const = 0;
/**
* Clear Holomap location position
* @param locationIdx Scene where position must be cleared
*/
virtual void clrHoloPos(int32 locationIdx) = 0;
virtual void holoTraj(int32 trajectoryIndex) = 0;
/** Load Holomap content */
virtual void initHoloDatas() = 0;
/** Main holomap process loop */
virtual void holoMap() = 0;
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,741 @@
/* 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 "twine/holomap_v1.h"
#include "common/algorithm.h"
#include "common/debug.h"
#include "common/memstream.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/types.h"
#include "graphics/palette.h"
#include "twine/audio/sound.h"
#include "twine/menu/interface.h"
#include "twine/parser/anim.h"
#include "twine/parser/holomap.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
// these are lba1 specific
#define HOLOMAP_ARROW (1 << 0)
#define HOLOMAP_VISITED (1 << 1)
#define HOLOMAP_UNK3 (1 << 2)
#define HOLOMAP_UNK4 (1 << 3)
#define HOLOMAP_UNK5 (1 << 4)
#define HOLOMAP_UNK6 (1 << 5)
#define HOLOMAP_CUBE_DONE (1 << 6)
#define HOLOMAP_CAN_FOCUS (1 << 7)
#define HOLOMAP_ACTIVE (HOLOMAP_CAN_FOCUS | HOLOMAP_ARROW)
static const float ZOOM_BIG_HOLO = 9500.0f;
static const float zDistanceTrajectory = 5300.0f;
static const int SIZE_CURSOR = 20;
int32 HolomapV1::distance(float distance) const {
const float w = (float)_engine->width() / (float)_engine->originalWidth();
const float h = (float)_engine->height() / (float)_engine->originalHeight();
const float f = MIN<float>(w, h);
return (int32)(distance / f);
}
int32 HolomapV1::scale(float val) const {
const float w = (float)_engine->width() / (float)_engine->originalWidth();
const float h = (float)_engine->height() / (float)_engine->originalHeight();
const float f = MIN<float>(w, h);
return (int32)(val * f);
}
bool HolomapV1::loadLocations() {
uint8 *locationsPtr = nullptr;
const int32 locationsSize = HQR::getAllocEntry(&locationsPtr, Resources::HQR_RESS_FILE, RESSHQR_HOLOARROWINFO);
if (locationsSize == 0) {
warning("Could not find holomap locations at index %i in %s", RESSHQR_HOLOARROWINFO, Resources::HQR_RESS_FILE);
return false;
}
Common::MemoryReadStream stream(locationsPtr, locationsSize, DisposeAfterUse::YES);
_numHoloPos = locationsSize / 8;
if (_numHoloPos > _engine->numHoloPos()) {
warning("Amount of locations (%i) exceeds the maximum of %i", _numHoloPos, _engine->numHoloPos());
return false;
}
_engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);
for (int32 i = 0; i < _numHoloPos; i++) {
_listHoloPos[i].alpha = stream.readSint16LE();
_listHoloPos[i].beta = stream.readSint16LE();
_listHoloPos[i].size = stream.readSint16LE();
_listHoloPos[i].mess = (TextId)stream.readSint16LE();
if (_engine->_text->getMenuText(_listHoloPos[i].mess, _listHoloPos[i].name, sizeof(_listHoloPos[i].name))) {
debug(2, "Scene %i: %s", i, _listHoloPos[i].name);
continue;
}
debug(2, "Could not get location text for index %i", i);
}
return true;
}
bool HolomapV1::setHoloPos(int32 locationIdx) {
assert(locationIdx >= 0 && locationIdx < _engine->numHoloPos());
if (_engine->isLBA1()) {
_engine->_gameState->_holomapFlags[locationIdx] = HOLOMAP_ACTIVE;
return true;
}
_engine->_gameState->_holomapFlags[locationIdx] = HOLOMAP_ACTIVE | HOLOMAP_VISITED;
return true;
}
void HolomapV1::clrHoloPos(int32 locationIdx) {
assert(locationIdx >= 0 && locationIdx <= ARRAYSIZE(_engine->_gameState->_holomapFlags));
_engine->_gameState->_holomapFlags[locationIdx] &= ~HOLOMAP_ACTIVE;
_engine->_gameState->_holomapFlags[locationIdx] |= HOLOMAP_CUBE_DONE;
}
void HolomapV1::initHoloDatas() {
constexpr TwineResource resource(Resources::HQR_RESS_FILE, RESSHQR_HOLOPAL);
_engine->_screens->loadCustomPalette(resource);
for (int32 n = 0; n < NUM_HOLOMAPCOLORS; n++) {
byte r, g, b;
_engine->_screens->_palettePcx.get(HOLOMAP_PALETTE_INDEX + n, r, g, b);
_rotPal[n * 3 + 0] = r;
_rotPal[n * 3 + 1] = g;
_rotPal[n * 3 + 2] = b;
}
for (int32 n = 0; n < NUM_HOLOMAPCOLORS - 1; n++) {
byte r, g, b;
_engine->_screens->_palettePcx.get(HOLOMAP_PALETTE_INDEX + n, r, g, b);
_rotPal[(NUM_HOLOMAPCOLORS + n) * 3 + 0] = r;
_rotPal[(NUM_HOLOMAPCOLORS + n) * 3 + 1] = g;
_rotPal[(NUM_HOLOMAPCOLORS + n) * 3 + 2] = b;
}
computeCoorMapping();
Common::SeekableReadStream *surfaceStream = HQR::makeReadStream(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOSURFACE));
if (surfaceStream == nullptr) {
error("Failed to load holomap surface");
}
computeCoorGlobe(surfaceStream);
delete surfaceStream;
_rotPalPos = 0;
}
void HolomapV1::computeCoorGlobe(Common::SeekableReadStream *holomapSurfaceStream) {
int holomapSurfaceArrayIdx = 0;
_engine->_renderer->setAngleCamera(0, 0, 0);
for (int alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += LBAAngles::ANGLE_11_25) {
const int32 rot = holomapSurfaceStream->readByte();
holomapSurfaceStream->seek(-1, SEEK_CUR);
for (int beta = 0; beta < LBAAngles::ANGLE_360; beta += LBAAngles::ANGLE_11_25) {
const int32 normal = 1000 + holomapSurfaceStream->readByte() * 2;
const IVec2 &rotVec = _engine->_renderer->rotate(normal, 0, alpha);
const IVec2 &rotVec2 = _engine->_renderer->rotate(rotVec.x, 0, beta);
const IVec3 &rotVec3 = _engine->_renderer->worldRotatePoint(IVec3(rotVec2.x, rotVec.y, rotVec2.y));
_holomapSurface[holomapSurfaceArrayIdx].x = rotVec3.x;
_holomapSurface[holomapSurfaceArrayIdx].y = rotVec3.y;
_holomapSurface[holomapSurfaceArrayIdx].z = rotVec3.z;
++holomapSurfaceArrayIdx;
}
const int32 normal = 1000 + rot * 2;
const IVec2 &rotVec = _engine->_renderer->rotate(normal, 0, alpha);
const IVec2 &rotVec2 = _engine->_renderer->rotate(rotVec.x, 0, 0);
const IVec3 &rotVec3 = _engine->_renderer->worldRotatePoint(IVec3(rotVec2.x, rotVec.y, rotVec2.y));
_holomapSurface[holomapSurfaceArrayIdx].x = rotVec3.x;
_holomapSurface[holomapSurfaceArrayIdx].y = rotVec3.y;
_holomapSurface[holomapSurfaceArrayIdx].z = rotVec3.z;
++holomapSurfaceArrayIdx;
}
assert(holomapSurfaceStream->eos());
}
void HolomapV1::computeCoorMapping() {
int projectedIndex = 0;
for (int32 alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += LBAAngles::ANGLE_11_25) {
for (int32 beta = 0; beta < LBAAngles::ANGLE_360; beta += LBAAngles::ANGLE_11_25) {
_projectedSurfacePositions[projectedIndex].x2 = ruleThree32(0, 255 * LBAAngles::ANGLE_90 + 255, LBAAngles::ANGLE_360 - 1, beta);
if (alpha == LBAAngles::ANGLE_90) {
_projectedSurfacePositions[projectedIndex].y2 = 255 * LBAAngles::ANGLE_90 + 255;
} else {
_projectedSurfacePositions[projectedIndex].y2 = ((alpha + LBAAngles::ANGLE_90) * LBAAngles::ANGLE_90) / 2;
}
++projectedIndex;
}
_projectedSurfacePositions[projectedIndex].x2 = 255 * LBAAngles::ANGLE_90 + 255;
if (alpha == LBAAngles::ANGLE_90) {
_projectedSurfacePositions[projectedIndex].y2 = 255 * LBAAngles::ANGLE_90 + 255;
} else {
_projectedSurfacePositions[projectedIndex].y2 = ((alpha + LBAAngles::ANGLE_90) * LBAAngles::ANGLE_90) / 2;
}
++projectedIndex;
}
}
void HolomapV1::computeGlobeProj() {
int holomapSortArrayIdx = 0;
int holomapSurfaceArrayIdx = 0;
_projectedSurfaceIndex = 0;
for (int32 alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += LBAAngles::ANGLE_11_25) {
for (int32 beta = 0; beta < LBAAngles::ANGLE_11_25; ++beta) {
IVec3 *vec = &_holomapSurface[holomapSurfaceArrayIdx++];
const IVec3 &destPos = _engine->_renderer->worldRotatePoint(*vec);
if (alpha != LBAAngles::ANGLE_90) {
_holomapSort[holomapSortArrayIdx].z = (int16)destPos.z;
_holomapSort[holomapSortArrayIdx].projectedPosIdx = _projectedSurfaceIndex;
++holomapSortArrayIdx;
}
const IVec3 &projPos = _engine->_renderer->projectPoint(destPos);
_projectedSurfacePositions[_projectedSurfaceIndex].x1 = projPos.x;
_projectedSurfacePositions[_projectedSurfaceIndex].y1 = projPos.y;
++_projectedSurfaceIndex;
}
IVec3 *vec = &_holomapSurface[holomapSurfaceArrayIdx++];
const IVec3 &destPos = _engine->_renderer->worldRotatePoint(*vec);
const IVec3 &projPos = _engine->_renderer->projectPoint(destPos);
_projectedSurfacePositions[_projectedSurfaceIndex].x1 = projPos.x;
_projectedSurfacePositions[_projectedSurfaceIndex].y1 = projPos.y;
++_projectedSurfaceIndex;
}
assert(holomapSortArrayIdx == ARRAYSIZE(_holomapSort));
assert(holomapSurfaceArrayIdx == ARRAYSIZE(_holomapSurface));
assert(_projectedSurfaceIndex == ARRAYSIZE(_projectedSurfacePositions));
Common::sort(_holomapSort, _holomapSort + ARRAYSIZE(_holomapSort), [](const HolomapSort &a, const HolomapSort &b) { return a.z < b.z; });
}
#define SURFACE_POS_OFFSET ((LBAAngles::ANGLE_360 / LBAAngles::ANGLE_11_25) + 1)
void HolomapV1::drawHoloMap(uint8 *holomapImage, uint32 holomapImageSize) {
computeGlobeProj();
for (int32 i = 0; i < ARRAYSIZE(_holomapSort); ++i) {
assert(_holomapSort[i].projectedPosIdx + 34 < _projectedSurfaceIndex);
const HolomapProjectedPos &pos1 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 0];
const HolomapProjectedPos &pos2 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 0 + SURFACE_POS_OFFSET];
const HolomapProjectedPos &pos3 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 1];
ComputedVertex vertexCoordinates[3];
vertexCoordinates[0].x = (int16)pos1.x1;
vertexCoordinates[0].y = (int16)pos1.y1;
vertexCoordinates[1].x = (int16)pos2.x1;
vertexCoordinates[1].y = (int16)pos2.y1;
vertexCoordinates[2].x = (int16)pos3.x1;
vertexCoordinates[2].y = (int16)pos3.y1;
if (isPolygonVisible(vertexCoordinates)) {
ComputedVertex textureCoordinates[3];
textureCoordinates[0].x = (int16)pos1.x2;
textureCoordinates[0].y = (int16)pos1.y2;
textureCoordinates[1].x = (int16)pos2.x2;
textureCoordinates[1].y = (int16)pos2.y2;
textureCoordinates[2].x = (int16)pos3.x2;
textureCoordinates[2].y = (int16)pos3.y2;
_engine->_renderer->asmTexturedTriangleNoClip(vertexCoordinates, textureCoordinates, holomapImage, holomapImageSize);
}
const HolomapProjectedPos &pos4 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 0 + SURFACE_POS_OFFSET];
const HolomapProjectedPos &pos5 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 1 + SURFACE_POS_OFFSET];
const HolomapProjectedPos &pos6 = _projectedSurfacePositions[_holomapSort[i].projectedPosIdx + 1];
vertexCoordinates[0].x = (int16)pos4.x1;
vertexCoordinates[0].y = (int16)pos4.y1;
vertexCoordinates[1].x = (int16)pos5.x1;
vertexCoordinates[1].y = (int16)pos5.y1;
vertexCoordinates[2].x = (int16)pos6.x1;
vertexCoordinates[2].y = (int16)pos6.y1;
if (isPolygonVisible(vertexCoordinates)) {
ComputedVertex textureCoordinates[3];
textureCoordinates[0].x = (int16)pos4.x2;
textureCoordinates[0].y = (int16)pos4.y2;
textureCoordinates[1].x = (int16)pos5.x2;
textureCoordinates[1].y = (int16)pos5.y2;
textureCoordinates[2].x = (int16)pos6.x2;
textureCoordinates[2].y = (int16)pos6.y2;
_engine->_renderer->asmTexturedTriangleNoClip(vertexCoordinates, textureCoordinates, holomapImage, holomapImageSize);
}
}
}
void HolomapV1::drawTitle(int32 x, int32 y, const char *title) {
const int32 size = _engine->_text->sizeFont(title);
const int32 textx = x - size / 2;
const int32 texty = y - 18;
_engine->_text->setFontColor(COLOR_WHITE);
_engine->_text->drawText(textx, texty, title);
int32 x0 = x - 630 / 2;
int32 x1 = x + 630 / 2;
int32 y0 = y - 40 / 2;
int32 y1 = y + 40 / 2;
_engine->copyBlockPhys(x0, y0, x1, y1);
}
void HolomapV1::drawHoloObj(const IVec3 &angle, int32 alpha, int32 beta, int16 size) {
_engine->_renderer->setAngleCamera(alpha, beta, 0);
const IVec3 &m = _engine->_renderer->worldRotatePoint(IVec3(0, 0, 1000 + size));
_engine->_renderer->setFollowCamera(0, 0, 0, angle.x, angle.y, angle.z, distance(zDistanceTrajectory));
_engine->_interface->unsetClip();
const IVec3 &m1 = _engine->_renderer->worldRotatePoint(m);
Common::Rect dirtyRect;
_engine->_renderer->renderIsoModel(m1, alpha, beta, LBAAngles::ANGLE_0, _engine->_resources->_holomapPointModelPtr, dirtyRect);
_engine->copyBlockPhys(dirtyRect);
}
void HolomapV1::renderHolomapVehicle(uint &frameNumber, RealValue &realRot, AnimTimerDataStruct &animTimerData, BodyData &bodyData, AnimData &animData) {
const int16 vbeta = realRot.getRealAngle(_engine->timerRef);
if (realRot.timeValue == 0) {
_engine->_movements->initRealAngle(LBAAngles::ANGLE_0, -LBAAngles::ANGLE_90, 500, &realRot);
}
if (_engine->_animations->setInterAnimObjet(frameNumber, animData, bodyData, &animTimerData)) {
frameNumber++;
if (frameNumber >= animData.getNbFramesAnim()) {
frameNumber = animData.getLoopFrame();
}
}
_engine->_renderer->setProjection(100, 100 + 300, 128, 900, 900);
_engine->_renderer->setFollowCamera(0, 0, 0, 60, 128, 0, distance(30000));
_engine->_renderer->setLightVector(-60, 128, 0);
// background of the vehicle
const Common::Rect rect(0, _engine->height() - 180, 200, _engine->height());
_engine->_interface->box(rect, COLOR_BLACK);
Common::Rect dummy;
_engine->_renderer->affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, vbeta, LBAAngles::ANGLE_0, bodyData, dummy);
_engine->copyBlockPhys(rect);
}
void HolomapV1::holoTraj(int32 trajectoryIndex) {
if (_engine->isDemo()) {
return;
}
debug("Draw trajectory index %i", trajectoryIndex);
const Trajectory *data = _engine->_resources->giveTrajPtr(trajectoryIndex);
if (data == nullptr) {
warning("Failed to load trajectory data for index %i", trajectoryIndex);
return;
}
_engine->saveTimer(false);
if (_engine->_screens->_flagPalettePcx)
_engine->_screens->fadeToBlack(_engine->_screens->_palettePcx);
else
_engine->_screens->fadeToBlack(_engine->_screens->_ptrPal);
_engine->_interface->unsetClip();
_engine->_screens->clearScreen();
initHoloDatas();
const int32 cameraPosX = _engine->width() / 2 + 80;
const int32 cameraPosY = _engine->height() / 2;
_engine->_renderer->setProjection(cameraPosX, cameraPosY, 128, 1024, 1024);
_engine->_renderer->setFollowCamera(0, 0, 0, data->angle.x, data->angle.y, data->angle.z, distance(zDistanceTrajectory));
constexpr TwineResource holomapImageRes(Resources::HQR_RESS_FILE, RESSHQR_HOLOIMG);
uint8 *holomapImagePtr = nullptr;
const int32 holomapImageSize = HQR::getAllocEntry(&holomapImagePtr, holomapImageRes);
if (holomapImageSize == 0) {
error("Failed to load holomap image");
}
drawHoloMap(holomapImagePtr, holomapImageSize);
const Location &loc = _listHoloPos[data->locationIdx];
drawHoloObj(data->angle, loc.alpha, loc.beta, 0);
RealValue move;
AnimTimerDataStruct animTimerData;
AnimData animData;
animData.loadFromHQR(Resources::HQR_RESS_FILE, data->getAnimation(), _engine->isLBA1());
BodyData bodyData;
bodyData.loadFromHQR(Resources::HQR_RESS_FILE, data->getModel(), _engine->isLBA1());
uint frameNumber = 0;
int32 frameTime = _engine->timerRef;
int16 trajAnimFrameIdx = 0;
bool flagpal = true;
_engine->_input->enableKeyMap(holomapKeyMapId);
for (;;) {
FrameMarker frame(_engine);
_engine->readKeys();
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
break;
}
if (!flagpal) {
// animate the water surface
_engine->setPalette(HOLOMAP_PALETTE_INDEX, NUM_HOLOMAPCOLORS, &_rotPal[3 * _rotPalPos]);
_rotPalPos++;
if (_rotPalPos == NUM_HOLOMAPCOLORS) {
_rotPalPos = 0;
}
}
renderHolomapVehicle(frameNumber, move, animTimerData, bodyData, animData);
// now render the holomap path
_engine->_renderer->setProjection(cameraPosX, cameraPosY, 128, 1024, 1024);
_engine->_renderer->setFollowCamera(0, 0, 0, data->angle.x, data->angle.y, data->angle.z, distance(zDistanceTrajectory));
_engine->_renderer->setLightVector(data->angle.x, data->angle.y, 0);
// animate the path from point 1 to point 2 by rendering a point model on each position
// on the globe every 40 timeunits
if (frameTime + 40 <= _engine->timerRef) {
frameTime = _engine->timerRef;
int32 alpha;
int32 beta;
if (trajAnimFrameIdx < data->numAnimFrames) {
alpha = data->positions[trajAnimFrameIdx].x;
beta = data->positions[trajAnimFrameIdx].y;
} else {
if (data->numAnimFrames < trajAnimFrameIdx) {
break;
}
alpha = loc.alpha;
beta = loc.beta;
}
drawHoloObj(data->angle, alpha, beta, 0);
++trajAnimFrameIdx;
}
if (flagpal) {
flagpal = false;
_engine->_screens->fadeToPal(_engine->_screens->_palettePcx);
}
++_engine->timerRef;
debugC(3, kDebugLevels::kDebugTimers, "Holomap time: %i", _engine->timerRef);
}
_engine->_screens->clearScreen();
_engine->_screens->fadeToBlack(_engine->_screens->_palettePcx);
_engine->_gameState->init3DGame();
_engine->_interface->restoreClip();
_engine->_text->initSceneTextBank();
_engine->_input->enableKeyMap(mainKeyMapId);
_engine->restoreTimer();
free(holomapImagePtr);
}
int32 HolomapV1::searchNextArrow(int32 num) const {
const int maxLocations = _engine->numHoloPos();
for (int32 n = num + 1; n < maxLocations; ++n) {
if ((_engine->_gameState->_holomapFlags[n] & HOLOMAP_ACTIVE) != 0u) {
return n;
}
}
return -1;
}
int32 HolomapV1::searchPrevArrow(int32 num) const {
int32 n;
const int maxLocations = _engine->numHoloPos();
if (num == -1) {
num = maxLocations;
}
for (n = num - 1; n >= 0; n--) {
if ((_engine->_gameState->_holomapFlags[n] & HOLOMAP_ACTIVE) != 0u) {
return n;
}
}
return -1;
}
void HolomapV1::drawListPos(int calpha, int cbeta, int cgamma, bool pos) {
int nbobjets = 0;
DrawListStruct listTri[MAX_HOLO_POS];
const int numCube = _engine->_scene->_numCube;
const int maxHoloPos = _engine->numHoloPos();
for (int n = 0; n < maxHoloPos; ++n) {
if (!(_engine->_gameState->_holomapFlags[n] & HOLOMAP_CAN_FOCUS) && n != numCube) {
continue;
}
const Location &ptrpos = _listHoloPos[n];
_engine->_renderer->setAngleCamera(ptrpos.alpha, ptrpos.beta, 0);
const IVec3 &m = _engine->_renderer->worldRotatePoint(IVec3(0, 0, 1000 + ptrpos.size));
const IVec3 &m1 = _engine->_renderer->worldRotatePoint(IVec3(0, 0, 1500));
_engine->_renderer->setInverseAngleCamera(calpha, cbeta, cgamma);
_engine->_renderer->setCameraRotation(0, 0, distance(ZOOM_BIG_HOLO));
const IVec3 &destPos3 = _engine->_renderer->worldRotatePoint(m);
const IVec3 &destPos4 = _engine->_renderer->worldRotatePoint(m1);
if (!pos) {
if (destPos4.z > destPos3.z) {
continue;
}
} else {
if (destPos4.z < destPos3.z) {
continue;
}
}
uint32 t = (uint32)_engine->_gameState->_holomapFlags[n] & HOLOMAP_ARROW;
if (n == numCube) {
t |= HOLOMAP_VISITED; // model type
}
DrawListStruct &drawList = listTri[nbobjets];
drawList.z = destPos3.z;
drawList.numObj = n;
drawList.num = t;
drawList.xw = m.x;
drawList.yw = m.y;
drawList.zw = m.z;
++nbobjets;
}
_engine->_redraw->sortDrawingList(listTri, nbobjets);
for (int i = 0; i < nbobjets; ++i) {
const DrawListStruct &drawList = listTri[i];
const uint32 flags = drawList.num;
const BodyData *ptr3do = nullptr;
if (flags == HOLOMAP_ARROW) {
ptr3do = &_engine->_resources->_holomapArrowPtr;
} else if (flags == HOLOMAP_VISITED) {
ptr3do = &_engine->_resources->_holomapTwinsenModelPtr;
} else if (flags == (HOLOMAP_ARROW | HOLOMAP_VISITED)) {
ptr3do = &_engine->_resources->_holomapTwinsenArrowPtr;
}
if (ptr3do != nullptr) {
const int32 alpha = _listHoloPos[drawList.numObj].alpha;
const int32 beta = _listHoloPos[drawList.numObj].beta;
Common::Rect dummy;
// first scene with twinsen model: x = 0, y = -497, z -764, a 432, b: 172
_engine->_renderer->affObjetIso(drawList.xw, drawList.yw, drawList.zw, alpha, beta, LBAAngles::ANGLE_0, *ptr3do, dummy);
}
}
}
void HolomapV1::drawCursor() {
// draw cursor
const int32 cameraPosY = scale(190);
const Common::Rect &targetRect = _engine->centerOnScreenX(SIZE_CURSOR * 2, 170, SIZE_CURSOR * 2);
_engine->_menu->drawRectBorders(targetRect.left, cameraPosY - 20, targetRect.right, cameraPosY + 20, 15, 15);
}
void HolomapV1::holoMap() {
const int32 alphaLightTmp = _engine->_scene->_alphaLight;
const int32 betaLightTmp = _engine->_scene->_betaLight;
const Graphics::Palette savepalette = _engine->_screens->_palettePcx;
_engine->saveTimer(false);
_engine->_screens->fadeToBlack(_engine->_screens->_ptrPal);
_engine->_sound->stopSamples();
_engine->_interface->unsetClip();
_engine->_screens->clearScreen();
initHoloDatas();
drawTitle(_engine->width() / 2, 25, "HoloMap");
const int32 cameraPosX = _engine->width() / 2;
const int32 cameraPosY = scale(190);
_engine->_renderer->setProjection(cameraPosX, cameraPosY, 128, 1024, 1024);
_engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);
_engine->_text->setFontCrossColor(COLOR_9);
constexpr TwineResource holomapImageRes(Resources::HQR_RESS_FILE, RESSHQR_HOLOIMG);
uint8 *holomapImagePtr = nullptr;
const int32 holomapImageSize = HQR::getAllocEntry(&holomapImagePtr, holomapImageRes);
if (holomapImageSize == 0) {
error("Failed to load holomap image");
}
_current = _engine->_scene->_numCube;
_otimer = _engine->timerRef;
_dalpha = ClampAngle(_listHoloPos[_current].alpha);
_dbeta = ClampAngle(_listHoloPos[_current].beta);
_calpha = _dalpha;
_cbeta = _dbeta;
_cgamma = 0;
_oalpha = _dalpha;
_obeta = _dbeta;
_automove = false;
_flagredraw = true;
_dialstat = true;
_flagpal = true;
_engine->_input->enableKeyMap(holomapKeyMapId);
for (;;) {
FrameMarker frame(_engine);
_engine->_input->readKeys();
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
break;
}
if (_engine->_input->toggleActionIfActive(TwinEActionType::HolomapPrev)) {
_current = searchPrevArrow(_current);
if (_current == -1) {
_current = _engine->_scene->_numCube;
}
_dialstat = true;
_oalpha = _calpha;
_obeta = _cbeta;
_otimer = _engine->timerRef;
_dalpha = _listHoloPos[_current].alpha;
_dbeta = _listHoloPos[_current].beta;
_automove = true;
_flagredraw = true;
debugC(1, TwinE::kDebugHolomap, "Holomap prev: %i (target angles: alpha %d, beta: %d)", _current, _dalpha, _dbeta);
} else if (_engine->_input->toggleActionIfActive(TwinEActionType::HolomapNext)) {
_current = searchNextArrow(_current);
if (_current == -1) {
_current = _engine->_scene->_numCube;
}
_dialstat = true;
_oalpha = _calpha;
_obeta = _cbeta;
_otimer = _engine->timerRef;
_dalpha = _listHoloPos[_current].alpha;
_dbeta = _listHoloPos[_current].beta;
_automove = true;
_flagredraw = true;
debugC(1, TwinE::kDebugHolomap, "Holomap next: %i (target angles: alpha %d, beta: %d)", _current, _dalpha, _dbeta);
}
if (!_automove) {
if (_engine->_input->isActionActive(TwinEActionType::HolomapDown)) {
_calpha += LBAAngles::ANGLE_2;
_calpha = ClampAngle(_calpha);
_flagredraw = true;
} else if (_engine->_input->isActionActive(TwinEActionType::HolomapUp)) {
_calpha -= LBAAngles::ANGLE_2;
_calpha = ClampAngle(_calpha);
_flagredraw = true;
}
if (_engine->_input->isActionActive(TwinEActionType::HolomapRight)) {
_cbeta += LBAAngles::ANGLE_2;
_cbeta = ClampAngle(_cbeta);
_flagredraw = true;
} else if (_engine->_input->isActionActive(TwinEActionType::HolomapLeft)) {
_cbeta -= LBAAngles::ANGLE_2;
_cbeta = ClampAngle(_cbeta);
_flagredraw = true;
}
}
if (_automove) {
const int32 dt = _engine->timerRef - _otimer;
_calpha = boundRuleThree(_oalpha, _dalpha, 75, dt);
_cbeta = boundRuleThree(_obeta, _dbeta, 75, dt);
_flagredraw = true;
debugC(1, TwinE::kDebugHolomap, "Holomap move: %i (target angles: alpha %d, beta: %d, current: alpha %d, beta %d)", _current, _dalpha, _dbeta, _calpha, _cbeta);
}
if (!_flagpal) {
// animate the water surface
_engine->setPalette(HOLOMAP_PALETTE_INDEX, NUM_HOLOMAPCOLORS, &_rotPal[3 * _rotPalPos]);
_rotPalPos++;
if (_rotPalPos == NUM_HOLOMAPCOLORS) {
_rotPalPos = 0;
}
_flagredraw = true;
}
if (_flagredraw) {
_flagredraw = false;
const Common::Rect &rect = _engine->centerOnScreenX(scale(300), 50, scale(280));
// clip reduces the bad effect of https://bugs.scummvm.org/ticket/12074
// but it's not part of the original code
_engine->_interface->memoClip();
_engine->_interface->setClip(rect);
_engine->_interface->box(rect, COLOR_BLACK);
_engine->_renderer->setInverseAngleCamera(_calpha, _cbeta, _cgamma);
_engine->_renderer->setLightVector(_calpha, _cbeta, 0);
drawListPos(_calpha, _cbeta, _cgamma, false);
_engine->_renderer->setInverseAngleCamera(_calpha, _cbeta, _cgamma);
_engine->_renderer->setCameraRotation(0, 0, distance(ZOOM_BIG_HOLO));
drawHoloMap(holomapImagePtr, holomapImageSize);
drawListPos(_calpha, _cbeta, _cgamma, true);
_engine->_interface->restoreClip();
if (_automove) {
drawCursor();
}
_engine->copyBlockPhys(rect);
}
if (_automove && _dalpha == _calpha && _dbeta == _cbeta) {
_automove = false;
debugC(1, TwinE::kDebugHolomap, "Holomap stop auto move");
}
if (_dialstat) {
_engine->_text->normalWinDial();
_engine->_text->setFontCrossColor(COLOR_WHITE);
_engine->_interface->box(_engine->_text->_dialTextBox, COLOR_BLACK);
_engine->saveFrontBuffer();
_engine->_text->commonOpenDial(_listHoloPos[_current].mess);
_engine->_text->initDialWindow();
_textState = ProgressiveTextState::ContinueRunning;
_dialstat = false;
}
if (_textState == ProgressiveTextState::ContinueRunning) {
_textState = _engine->_text->nextDialChar();
if (_textState != ProgressiveTextState::ContinueRunning) {
_engine->_text->fadeInRemainingChars();
_engine->_text->closeDial();
}
}
++_engine->timerRef;
debugC(3, kDebugLevels::kDebugTimers, "Holomap time: %i", _engine->timerRef);
if (_flagpal) {
_flagpal = false;
_engine->_screens->fadeToPal(_engine->_screens->_palettePcx);
}
}
_engine->_text->_flagMessageShade = true;
_engine->_screens->fadeToBlack(_engine->_screens->_palettePcx);
_engine->_scene->_alphaLight = alphaLightTmp;
_engine->_scene->_betaLight = betaLightTmp;
_engine->_gameState->init3DGame();
_engine->_input->enableKeyMap(mainKeyMapId);
_engine->_text->initSceneTextBank();
_engine->restoreTimer();
_engine->_screens->_palettePcx = savepalette;
free(holomapImagePtr);
}
const char *HolomapV1::getLocationName(int index) const {
assert(index >= 0 && index <= ARRAYSIZE(_listHoloPos));
return _listHoloPos[index].name;
}
} // namespace TwinE

144
engines/twine/holomap_v1.h Normal file
View File

@@ -0,0 +1,144 @@
/* 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 TWINE_HOLOMAPV1_H
#define TWINE_HOLOMAPV1_H
#include "twine/holomap.h"
#include "twine/shared.h"
#include "twine/text.h"
#define NUM_HOLOMAPCOLORS 32
#define HOLOMAP_PALETTE_INDEX (12*16)
namespace TwinE {
/**
* The Holomap shows the hero position. The arrows (@c RESSHQR_HOLOARROWMDL) represent important places in your quest - they automatically disappear once that part of
* the quest is done (@c clrHoloPos()). You can rotate the holoamp by pressing ctrl+cursor keys - but only using the cursor keys, you can scroll through the
* text for the visible arrows.
*/
class HolomapV1 : public Holomap {
private:
using Super = Holomap;
struct Location {
int16 alpha;
int16 beta;
int16 size;
TextId mess = TextId::kNone;
char name[30] = "";
};
IVec3 _holomapSurface[561];
// original game size: 2244 (lba1)
struct HolomapSort {
int16 z = 0;
uint16 projectedPosIdx = 0;
};
HolomapSort _holomapSort[16 * 32];
struct HolomapProjectedPos {
uint16 x1 = 0;
uint16 y1 = 0;
uint16 x2 = 0;
uint16 y2 = 0;
};
HolomapProjectedPos _projectedSurfacePositions[561];
int _projectedSurfaceIndex = 0;
// float _distanceModifier = 1.0f;
int32 _numHoloPos = 0;
Location _listHoloPos[MAX_HOLO_POS];
int32 _rotPalPos = 0;
uint8 _rotPal[NUMOFCOLORS * 3]{0};
void drawTitle(int32 centerx, int32 top, const char *title);
int32 searchNextArrow(int32 num) const;
int32 searchPrevArrow(int32 num) const;
void drawCursor(); // DrawCurseur
void drawListPos(int xRot, int yRot, int zRot, bool lower);
/**
* Renders a holomap path with single path points appearing slowly one after another
*/
void drawHoloObj(const IVec3 &angle, int32 x, int32 y, int16 size);
void computeCoorGlobe(Common::SeekableReadStream *holomapSurfaceStream);
void computeCoorMapping();
void computeGlobeProj();
void drawHoloMap(uint8 *holomapImage, uint32 holomapImageSize);
void renderHolomapVehicle(uint &frameNumber, RealValue &move, AnimTimerDataStruct &animTimerData, BodyData &bodyData, AnimData &animData);
/**
* Controls the size/zoom of the holomap planet
*/
int32 distance(float distance) const;
int32 scale(float val) const;
public:
HolomapV1(TwinEEngine *engine) : Super(engine) {}
virtual ~HolomapV1() = default;
int32 _current = 0;
int32 _otimer = 0;
int32 _dalpha = 0;
int32 _dbeta = 0;
int32 _calpha = 0;
int32 _cbeta = 0;
int32 _cgamma = 0;
int32 _oalpha = 0;
int32 _obeta = 0;
bool _automove = false;
bool _flagredraw = false;
bool _dialstat = false;
bool _flagpal = false;
ProgressiveTextState _textState = ProgressiveTextState::End;
/**
* Set Holomap location position
* @param locationIdx Scene where position must be set
*/
bool setHoloPos(int32 locationIdx) override;
bool loadLocations() override;
const char *getLocationName(int index) const override;
/**
* Clear Holomap location position
* @param locationIdx Scene where position must be cleared
*/
void clrHoloPos(int32 locationIdx) override;
void holoTraj(int32 trajectoryIndex) override;
/** Load Holomap content */
void initHoloDatas() override;
/** Main holomap process loop */
void holoMap() override;
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,56 @@
/* 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 "twine/holomap_v2.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
namespace TwinE {
bool HolomapV2::setHoloPos(int32 locationIdx) {
return false;
}
bool HolomapV2::loadLocations() {
// _locations[MAX_OBJECTIF + 67].FlagHolo = 1; // Desert Globe
return HQR::getEntry((uint8 *)_locations, Resources::HQR_HOLOMAP_FILE, RESSHQR_ARROWBIN) != 0;
}
const char *HolomapV2::getLocationName(int index) const {
if (index >= 0 && index < ARRAYSIZE(_locations)) {
// TODO: return _locations[index].;
}
return "";
}
void HolomapV2::clrHoloPos(int32 locationIdx) {
}
void HolomapV2::holoTraj(int32 trajectoryIndex) {
}
void HolomapV2::initHoloDatas() {
}
void HolomapV2::holoMap() {
}
} // namespace TwinE

View File

@@ -0,0 +1,88 @@
/* 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 TWINE_HOLOMAPV2_H
#define TWINE_HOLOMAPV2_H
#include "twine/holomap.h"
#define MAX_OBJECTIF 50
#define MAX_CUBE 255
namespace TwinE {
/**
* The Holomap shows the hero position. The arrows (@c RESSHQR_HOLOARROWMDL) represent important places in your quest - they automatically disappear once that part of
* the quest is done (@c clrHoloPos()). You can rotate the holoamp by pressing ctrl+cursor keys - but only using the cursor keys, you can scroll through the
* text for the visible arrows.
*/
class HolomapV2 : public Holomap {
private:
using Super = Holomap;
public:
HolomapV2(TwinEEngine *engine) : Super(engine) {}
virtual ~HolomapV2() = default;
struct Location {
int32 X = 0; // Position Island X Y Z
int32 Y = 0;
int32 Z = 0;
int32 Alpha = 0; // Position Planet Alpha, Beta and Altitude
int32 Beta = 0;
int32 Alt = 0;
int32 Mess = 0;
int8 ObjFix = 0; // Eventual Obj Inventory 3D (FREE NOT USED!)
uint8 FlagHolo = 0u; // Flag for Planet display, active, etc.
uint8 Planet = 0u;
uint8 Island = 0u;
};
static_assert(sizeof(Location) == 32, "Invalid Location size");
Location _locations[MAX_OBJECTIF + MAX_CUBE];
/**
* Set Holomap location position
* @param locationIdx Scene where position must be set
*/
bool setHoloPos(int32 locationIdx) override;
bool loadLocations() override;
const char *getLocationName(int index) const override;
/**
* Clear Holomap location position
* @param locationIdx Scene where position must be cleared
*/
void clrHoloPos(int32 locationIdx) override;
void holoTraj(int32 trajectoryIndex) override;
/** Load Holomap content */
void initHoloDatas() override;
/** Main holomap process loop */
void holoMap() override;
};
} // namespace TwinE
#endif

174
engines/twine/input.cpp Normal file
View 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/>.
*
*/
#include "twine/input.h"
#include "backends/keymapper/keymapper.h"
#include "common/events.h"
#include "common/system.h"
#include "twine/scene/actor.h"
#include "twine/twine.h"
namespace TwinE {
const char *mainKeyMapId = "mainKeyMap";
const char *uiKeyMapId = "uiKeyMap";
const char *cutsceneKeyMapId = "cutsceneKeyMap";
const char *holomapKeyMapId = "holomapKeyMap";
ScopedKeyMap::ScopedKeyMap(TwinEEngine *engine, const char *id) : _engine(engine) {
_changed = _engine->_input->enableAdditionalKeyMap(id, true);
_keymap = id;
}
ScopedKeyMap::~ScopedKeyMap() {
if (_changed) {
_engine->_input->enableAdditionalKeyMap(_keymap.c_str(), false);
}
}
Input::Input(TwinEEngine *engine) : _engine(engine) {
resetLastHoveredMousePosition();
}
bool Input::isActionActive(TwinEActionType actionType, bool onlyFirstTime) const {
if (onlyFirstTime) {
return _actionStates[actionType] == 1;
}
return _actionStates[actionType] > 0;
}
bool Input::toggleActionIfActive(TwinEActionType actionType) {
if (_actionStates[actionType] > 0) {
_actionStates[actionType] = 0;
return true;
}
return false;
}
void Input::resetActionStates() {
for (int i = 0; i < TwinEActionType::Max; ++i) {
_actionStates[i] = false;
}
}
bool Input::toggleAbortAction() {
bool abortState = false;
abortState |= toggleActionIfActive(TwinEActionType::CutsceneAbort);
abortState |= toggleActionIfActive(TwinEActionType::UIAbort);
abortState |= toggleActionIfActive(TwinEActionType::Escape);
abortState |= toggleActionIfActive(TwinEActionType::HolomapAbort);
return abortState;
}
bool Input::isQuickBehaviourActionActive() const {
return isActionActive(TwinEActionType::QuickBehaviourNormal) || isActionActive(TwinEActionType::QuickBehaviourAthletic) || isActionActive(TwinEActionType::QuickBehaviourAggressive) || isActionActive(TwinEActionType::QuickBehaviourDiscreet);
}
bool Input::isMoveOrTurnActionActive() const {
return isActionActive(TwinEActionType::TurnLeft) || isActionActive(TwinEActionType::TurnRight) || isActionActive(TwinEActionType::MoveBackward) || isActionActive(TwinEActionType::MoveForward);
}
bool Input::isHeroActionActive() const {
return isActionActive(TwinEActionType::ExecuteBehaviourAction);
}
bool Input::resetHeroActions() {
return toggleActionIfActive(TwinEActionType::ExecuteBehaviourAction) || toggleActionIfActive(TwinEActionType::SpecialAction);
}
bool Input::enableAdditionalKeyMap(const char *id, bool enable) {
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
Common::Keymap *keymap = keymapper->getKeymap(id);
if (keymap == nullptr) {
return false;
}
const bool changed = keymap->isEnabled() != enable;
keymap->setEnabled(enable);
return changed;
}
void Input::enableKeyMap(const char *id) {
if (_currentKeyMap == id) {
return;
}
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
const Common::KeymapArray &keymaps = keymapper->getKeymaps();
for (Common::Keymap *keymap : keymaps) {
const Common::String &keymapId = keymap->getId();
if (keymapId == mainKeyMapId || keymapId == uiKeyMapId || keymapId == cutsceneKeyMapId || keymapId == holomapKeyMapId) {
keymap->setEnabled(keymapId == id);
}
}
_currentKeyMap = id;
debugC(1, TwinE::kDebugInput, "enable keymap %s", id);
}
void Input::processCustomEngineEventStart(const Common::Event &event) {
_actionStates[event.customType] = 1 + event.kbdRepeat;
debugC(2, TwinE::kDebugInput, "twine custom event type start: %i", event.customType);
}
void Input::processCustomEngineEventEnd(const Common::Event &event) {
_actionStates[event.customType] = 0;
debugC(2, TwinE::kDebugInput, "twine custom event type end: %i", event.customType);
}
void Input::readKeys() {
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
processCustomEngineEventEnd(event);
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
processCustomEngineEventStart(event);
break;
default:
break;
}
}
}
Common::Point Input::getMousePositions() const {
return g_system->getEventManager()->getMousePos();
}
bool Input::isMouseHovering(const Common::Rect &rect, bool onlyIfMoved) {
if (!_engine->_cfgfile.Mouse) {
return false;
}
const Common::Point &point = getMousePositions();
if (onlyIfMoved && _lastMousePos == point) {
return false;
}
if (rect.contains(point)) {
_lastMousePos = point;
return true;
}
return false;
}
void Input::resetLastHoveredMousePosition() {
_lastMousePos = Common::Point(-1, -1);
}
} // namespace TwinE

170
engines/twine/input.h Normal file
View File

@@ -0,0 +1,170 @@
/* 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 TWINE_KEYBOARD_H
#define TWINE_KEYBOARD_H
#include "common/events.h"
#include "common/keyboard.h"
#include "common/scummsys.h"
#include "common/util.h"
#include "common/rect.h"
namespace TwinE {
class TwinEEngine;
extern const char *mainKeyMapId;
extern const char *uiKeyMapId;
extern const char *cutsceneKeyMapId;
extern const char *holomapKeyMapId;
enum TwinEActionType {
Pause,
DebugGridCameraPressUp,
DebugGridCameraPressDown,
DebugGridCameraPressLeft,
DebugGridCameraPressRight,
QuickBehaviourNormal,
QuickBehaviourAthletic,
QuickBehaviourAggressive,
QuickBehaviourDiscreet,
ChangeBehaviourNormal,
ChangeBehaviourAthletic,
ChangeBehaviourAggressive,
ChangeBehaviourDiscreet,
ExecuteBehaviourAction,
BehaviourMenu,
OptionsMenu,
RecenterScreenOnTwinsen,
UseSelectedObject,
ThrowMagicBall,
MoveForward,
MoveBackward,
TurnRight,
TurnLeft,
UseProtoPack,
OpenHolomap,
InventoryMenu,
SpecialAction,
SceneryZoom,
Escape,
UIEnter,
UIAbort,
UILeft,
UIRight,
UIUp,
UIDown,
UINextPage,
CutsceneAbort,
HolomapAbort,
HolomapLeft,
HolomapRight,
HolomapUp,
HolomapDown,
HolomapNext,
HolomapPrev,
Max
};
/**
* @brief Activates the given key map id that is registered in the meta engine
*/
class ScopedKeyMap {
private:
TwinEEngine* _engine;
bool _changed;
Common::String _keymap;
public:
ScopedKeyMap(TwinEEngine* engine, const char *id);
~ScopedKeyMap();
};
class Input {
private:
TwinEEngine *_engine;
Common::String _currentKeyMap;
uint8 _actionStates[TwinEActionType::Max]{false};
Common::Point _lastMousePos;
public:
Input(TwinEEngine *engine);
/**
* @brief Dependent on the context we are currently in the game, we might want to disable certain keymaps.
* Like disabling ui keymaps when we are in-game - or vice versa.
*/
void enableKeyMap(const char *id);
bool enableAdditionalKeyMap(const char *id, bool enable);
const Common::String currentKeyMap() const;
/**
* @param onlyFirstTime If this is set to @c true, repeating key press events are not taken into account here
* This means, that even if the key is held down, this will return @c false. @c false as value for this parameter
* will return @c true also for repeating key presses.
*
* @sa isPressed()
*/
bool isActionActive(TwinEActionType actionType, bool onlyFirstTime = true) const;
void resetLastHoveredMousePosition();
bool isMouseHovering(const Common::Rect &rect, bool onlyIfMoved = true);
/**
* @brief If the action is active, the internal state is reset and a following call of this method won't return
* @c true anymore
*/
bool toggleActionIfActive(TwinEActionType actionType);
void resetActionStates();
bool toggleAbortAction();
bool isQuickBehaviourActionActive() const;
bool isMoveOrTurnActionActive() const;
bool isHeroActionActive() const; // MyFire & F_SPACE
bool resetHeroActions();
/**
* Gets mouse positions
* @param mouseData structure that contains mouse position info
*/
Common::Point getMousePositions() const;
/**
* @brief Updates the internal action states
*/
void readKeys();
void processCustomEngineEventStart(const Common::Event& event);
void processCustomEngineEventEnd(const Common::Event& event);
};
inline const Common::String Input::currentKeyMap() const {
return _currentKeyMap;
}
} // namespace TwinE
#endif

View File

@@ -0,0 +1,214 @@
/* 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 "twine/menu/interface.h"
#include "graphics/managed_surface.h"
#include "graphics/primitives.h"
#include "twine/twine.h"
namespace TwinE {
Interface::Interface(TwinEEngine *engine) : _engine(engine) {}
bool Interface::drawLine(int32 x0, int32 y0, int32 x1, int32 y1, uint8 color) {
// always from left to right
if (x0 > x1) {
SWAP(x0, x1);
SWAP(y0, y1);
}
const int32 cright = _clip.right;
const int32 cleft = _clip.left;
const int32 cbottom = _clip.bottom;
const int32 ctop = _clip.top;
uint16 clipFlags;
do {
if (x0 > cright || x1 < cleft) {
return false;
}
int32 dx = x1 - x0;
int32 dy = y1 - y0;
clipFlags = 0;
if (x0 < cleft) {
clipFlags |= 0x100;
}
if (y0 < ctop) {
clipFlags |= 0x800;
} else if (y0 > cbottom) {
clipFlags |= 0x400;
}
if (x1 > cright) {
clipFlags |= 0x2;
}
if (y1 < ctop) {
clipFlags |= 0x8;
} else if (y1 > cbottom) {
clipFlags |= 0x4;
}
if (clipFlags & (clipFlags >> 8)) {
return false;
}
if (clipFlags) {
if (clipFlags & 0x100) {
y0 += ((cleft - x0) * dy) / dx;
x0 = cleft;
} else if (clipFlags & 0x800) {
x0 += ((ctop - y0) * dx) / dy;
y0 = ctop;
} else if (clipFlags & 0x400) {
x0 += ((cbottom - y0) * dx) / dy;
y0 = cbottom;
} else if (clipFlags & 0x2) {
y1 = (((cright - x0) * dy) / dx) + y0;
x1 = cright;
} else if (clipFlags & 0x8) {
x1 = (((ctop - y0) * dx) / dy) + x0;
y1 = ctop;
} else if (clipFlags & 0x4) {
x1 = (((cbottom - y0) * dx) / dy) + x0;
y1 = cbottom;
}
}
} while (clipFlags);
int16 lineOffset = (int16)_engine->width();
x1 -= x0;
y1 -= y0;
if (y1 < 0) {
lineOffset = -lineOffset;
y1 = -y1;
}
byte *out = (byte *)_engine->_frontVideoBuffer.getBasePtr(x0, y0);
if (x1 < y1) {
// vertical
SWAP(x1, y1);
int32 dy = x1 << 1;
y0 = x1;
y1 <<= 1;
x1++;
do {
*out = color;
y0 -= y1;
out += lineOffset;
if (y0 < 0) {
y0 += dy;
out++;
}
} while (--x1);
} else {
// horizontal
int32 dy = x1 << 1;
y0 = x1;
y1 <<= 1;
x1++;
do {
*out++ = color;
y0 -= y1;
if (y0 < 0) {
y0 += dy;
out += lineOffset;
}
} while (--x1);
}
_engine->_frontVideoBuffer.addDirtyRect(Common::Rect(MIN<int16>(x0, x1), MIN<int16>(y0, y1), MAX<int16>(x0, x1), MAX<int16>(y0, y1)));
return true;
}
void Interface::blitBox(const Common::Rect &rect, const Graphics::ManagedSurface &source, Graphics::ManagedSurface &dest) {
Common::Rect r(rect);
r.right += 1;
r.bottom += 1;
dest.blitFrom(source, r, Common::Point(rect.left, rect.top));
}
void Interface::shadeBox(const Common::Rect &rect, int32 colorAdj) {
Common::Rect r = rect;
r.clip(_engine->rect());
if (r.isEmpty()) {
return;
}
uint8 *pos = (uint8*)_engine->_frontVideoBuffer.getBasePtr(0, r.top);
for (int32 y = r.top; y <= r.bottom; ++y) {
for (int32 x = r.left; x <= r.right; ++x) {
const int8 color = (pos[x] & 0x0F) - colorAdj;
const int8 color2 = pos[x] & 0xF0;
if (color < 0) {
pos[x] = color2;
} else {
pos[x] = color + color2;
}
}
pos += _engine->_frontVideoBuffer.pitch;
}
_engine->_frontVideoBuffer.addDirtyRect(r);
}
void Interface::box(const Common::Rect &rect, uint8 colorIndex) { // Box
if (!rect.isValidRect()) {
return;
}
Common::Rect clipped(rect.left, rect.top, rect.right + 1, rect.bottom + 1);
if (_clip.isValidRect()) {
clipped.clip(_clip);
}
_engine->_frontVideoBuffer.fillRect(clipped, colorIndex);
}
bool Interface::setClip(const Common::Rect &rect) {
if (!_clip.isValidRect()) {
return false;
}
_clip = rect;
_clip.clip(_engine->rect());
return true;
}
void Interface::memoClip() {
_memoClip = _clip;
}
void Interface::restoreClip() {
_clip = _memoClip;
}
void Interface::unsetClip() {
_clip = _engine->rect();
}
} // namespace TwinE

View 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/>.
*
*/
#ifndef TWINE_INTERFACE_H
#define TWINE_INTERFACE_H
#include "common/scummsys.h"
#include "common/rect.h"
namespace Graphics {
class ManagedSurface;
}
namespace TwinE {
class TwinEEngine;
class Interface {
private:
TwinEEngine *_engine;
Common::Rect _memoClip;
public:
Interface(TwinEEngine *engine);
// ClipXmin, ClipXmax, ClipYmin, ClipYmax
Common::Rect _clip { 0, 0, 0, 0 };
bool _animateTexture = false; // lba2: AnimateTexture
/**
* Draw button line
* @param startWidth width value where the line starts
* @param startHeight height value where the line starts
* @param endWidth width value where the line ends
* @param endHeight height value where the line ends
* @param lineColor line color in the current palette
*/
bool drawLine(int32 startWidth, int32 startHeight, int32 endWidth, int32 endHeight, uint8 lineColor);
/**
* Blit button box from working buffer to front buffer
* @param source source screen buffer, in this case working buffer
* @param dest destination screen buffer, in this case front buffer
*/
void blitBox(const Common::Rect &rect, const Graphics::ManagedSurface &source, Graphics::ManagedSurface &dest);
/**
* Draws inside buttons transparent area
* @param colorAdj index to adjust the transparent box color
*/
void shadeBox(const Common::Rect &rect, int32 colorAdj);
void box(const Common::Rect &rect, uint8 colorIndex);
bool setClip(const Common::Rect &rect);
void memoClip(); // saveTextWindow
void restoreClip(); // loadSavedTextWindow
void unsetClip();
};
} // namespace TwinE
#endif

1488
engines/twine/menu/menu.cpp Normal file

File diff suppressed because it is too large Load Diff

259
engines/twine/menu/menu.h Normal file
View File

@@ -0,0 +1,259 @@
/* 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 TWINE_MENU_H
#define TWINE_MENU_H
#include "twine/twine.h"
#include "twine/text.h"
namespace TwinE {
#define MAX_BUTTONS 10
#define PLASMA_WIDTH 320
#define PLASMA_HEIGHT 50
#define kDemoMenu 9999
#define kQuitEngine 9998
class BodyData;
class SpriteData;
class MenuSettings {
private:
enum MenuSettingsType {
// button number
MenuSettings_CurrentLoadedButton = 0,
// is used to calc the height where the first button will appear
MenuSettings_NumberOfButtons = 1,
MenuSettings_ButtonsBoxHeight = 2,
MenuSettings_TextBankId = 3,
MenuSettings_FirstButtonState,
MenuSettings_FirstButton
};
int16 _settings[4 + MAX_BUTTONS * 2] {0};
Common::String _buttonTexts[MAX_BUTTONS];
int8 _activeButtonIdx = 0;
public:
TextId getButtonTextId(int buttonIndex) const {
return (TextId)_settings[MenuSettings_FirstButton + buttonIndex * 2];
}
void reset() {
for (int32 i = 0; i < MAX_BUTTONS; ++i) {
_buttonTexts[i] = "";
}
_settings[MenuSettings_NumberOfButtons] = 0;
setButtonsBoxHeight(0);
setActiveButton(0);
}
// used to calc the height where the first button will appear
void setButtonsBoxHeight(int16 height) {
_settings[MenuSettings_ButtonsBoxHeight] = height;
}
void setActiveButton(int16 buttonIdx) {
_activeButtonIdx = buttonIdx;
_settings[MenuSettings_CurrentLoadedButton] = buttonIdx;
}
void setActiveButtonTextId(TextId textIndex) {
setButtonTextId(getActiveButton(), textIndex);
}
void setButtonTextId(int16 buttonIdx, TextId textIndex) {
_settings[MenuSettings_FirstButton + buttonIdx * 2] = (int16)textIndex;
_buttonTexts[buttonIdx].clear();
}
TextId getActiveButtonTextId() const {
return getButtonTextId(getActiveButton());
}
int16 getActiveButtonState() const {
return getButtonState(getActiveButton());
}
int16 getButtonState(int buttonIndex) const {
return _settings[MenuSettings_FirstButtonState + buttonIndex * 2];
}
const char *getButtonText(Text *text, int buttonIndex);
int16 getActiveButton() const {
return _activeButtonIdx;
}
int16 getButtonBoxHeight() const {
return _settings[MenuSettings_ButtonsBoxHeight];
}
int16 getButtonCount() const {
return _settings[MenuSettings_NumberOfButtons];
}
void setTextBankId(TextBankId textBankIndex) {
_settings[MenuSettings_TextBankId] = (int16)textBankIndex;
}
void addButton(TextId textId, int16 state = 0) {
const int16 i = _settings[MenuSettings_NumberOfButtons];
_settings[i * 2 + MenuSettings_FirstButtonState] = state;
_settings[i * 2 + MenuSettings_FirstButton] = (int16)textId;
++_settings[MenuSettings_NumberOfButtons];
}
void addButton(const char *text, int16 state = 0) {
const int16 i = _settings[MenuSettings_NumberOfButtons];
_settings[i * 2 + MenuSettings_FirstButtonState] = state;
// will return the button index
_settings[i * 2 + MenuSettings_FirstButton] = i;
_buttonTexts[i] = text;
++_settings[MenuSettings_NumberOfButtons];
}
};
class Menu {
private:
TwinEEngine *_engine;
/** Hero behaviour menu entity */
BodyData *_behaviourEntity = nullptr;
/** Behaviour menu anim state */
uint _behaviourAnimState[4]; // winTab
/** Behaviour menu anim data pointer */
AnimTimerDataStruct _behaviourAnimData[4];
int32 _inventorySelectedColor = COLOR_BLACK;
int32 _inventorySelectedItem = 0; // currentSelectedObjectInInventory
/** Plasma Effect pointer to file content: RESS.HQR:51 */
uint8 *_plasmaEffectPtr = nullptr;
MenuSettings _giveUpMenuWithSaveState;
MenuSettings _volumeMenuState;
MenuSettings _saveManageMenuState;
MenuSettings _giveUpMenuState;
MenuSettings _mainMenuState;
MenuSettings _newGameMenuState;
MenuSettings _advOptionsMenuState;
MenuSettings _optionsMenuState;
MenuSettings _languageMenuState;
// objectRotation
int16 _itemAngle[NUM_INVENTORY_ITEMS];
/** Behaviour menu move pointer */
RealValue _moveMenu;
/**
* Draws main menu button
* @param buttonId current button identification from menu settings
* @param dialText
* @param hover flag to know if should draw as a hover button or not
*/
void drawButtonGfx(const MenuSettings *menuSettings, const Common::Rect &rect, int32 buttonId, const char *dialText, bool hover);
void plasmaEffectRenderFrame();
/**
* Process the menu button draw
* @param data menu settings array
* @param mode flag to know if should draw as a hover button or not
*/
int16 drawButtons(MenuSettings *menuSettings, bool hover);
/** Used to run the advanced options menu */
int32 advoptionsMenu();
/** Used to run the volume menu */
int32 volumeOptions();
int32 languageMenu();
/** Used to run the save game management menu */
int32 savemanageMenu();
void drawInfoMenu(int16 left, int16 top, int16 width);
Common::Rect calcBehaviourRect(int32 left, int32 top, HeroBehaviourType behaviour) const;
bool isBehaviourHovered(int32 left, int32 top, HeroBehaviourType behaviour) const;
void drawBehaviour(int32 left, int32 top, HeroBehaviourType behaviour, int32 angle, bool cantDrawBox);
void drawListInventory(int32 left, int32 top);
void prepareAndDrawBehaviour(int32 left, int32 top, int32 angle, HeroBehaviourType behaviour); // DrawComportement
void drawBehaviourMenu(int32 left, int32 top, int32 angle); // DrawMenuComportement
Common::Rect calcItemRect(int32 left, int32 top, int32 item, int32 *centerX = nullptr, int32 *centerY = nullptr) const;
// draw the 2d sprite of the item
void drawOneInventory(int32 left, int32 top, int32 item);
void drawSpriteAndString(int32 left, int32 top, const SpriteData &spriteData, const Common::String &str, int32 color = COLOR_GOLD);
public:
Menu(TwinEEngine *engine);
~Menu();
/**
* Process the plasma effect
* @param color plasma effect start color
*/
void processPlasmaEffect(const Common::Rect &rect, int32 color);
void drawHealthBar(int32 left, int32 right, int32 top, int32 barLeftPadding, int32 barHeight);
void drawCloverLeafs(int32 newBoxLeft, int32 boxRight, int32 top);
void drawMagicPointsBar(int32 left, int32 right, int32 top, int32 barLeftPadding, int32 barHeight);
void drawCoins(int32 left, int32 top);
void drawKeys(int32 left, int32 top);
/**
* Draw the rect lines without filling the area
* @param left start width to draw the button
* @param top start height to draw the button
* @param right end width to draw the button
* @param bottom end height to draw the button
*/
void drawRectBorders(int32 left, int32 top, int32 right, int32 bottom, int32 colorLeftTop = COLOR_79, int32 colorRightBottom = COLOR_73);
void drawRectBorders(const Common::Rect &rect, int32 colorLeftTop = COLOR_79, int32 colorRightBottom = COLOR_73);
/**
* Where the main menu options are processed
* @param menuSettings menu settings array with the information to build the menu options
* @return pressed menu button identification
*/
void menuDemo();
int32 doGameMenu(MenuSettings *menuSettings);
bool init();
/** Used to run the main menu */
EngineState run();
/** Used to run the in-game give-up menu */
int32 quitMenu();
void inGameOptionsMenu();
/** Used to run the options menu */
int32 optionsMenu();
/** Process hero behaviour menu */
void processBehaviourMenu(bool behaviourMenu); // MenuComportement
int32 newGameClassicMenu();
/** Process in-game inventory menu */
void inventory();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,450 @@
/* 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 "twine/menu/menuoptions.h"
#include "common/error.h"
#include "common/keyboard.h"
#include "common/str-array.h"
#include "common/system.h"
#include "common/util.h"
#include "savestate.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/movies.h"
#include "twine/scene/gamestate.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/menu/menu.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/scene.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
static const char allowedCharIndex[] = " ABCDEFGHIJKLM.NOPQRSTUVWXYZ-abcdefghijklm?nopqrstuvwxyz!0123456789\040\b\r\0";
void MenuOptions::newGame() {
_engine->setTotalPlayTime(0);
_engine->_music->stopMusic();
_engine->_sound->stopSamples();
if (_engine->isLBA1()) {
int32 tmpFlagDisplayText = _engine->_cfgfile.FlagDisplayText;
_engine->_cfgfile.FlagDisplayText = true;
// intro screen 1 - twinsun
_engine->_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 15, 16));
_engine->_text->_flagMessageShade = false;
_engine->_text->_renderTextTriangle = true;
_engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);
_engine->_text->bigWinDial();
_engine->_text->setFontCrossColor(COLOR_WHITE);
bool aborted = _engine->_text->drawTextProgressive(TextId::kIntroText1);
// intro screen 2
if (!aborted) {
_engine->_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 17, 18));
aborted |= _engine->_text->drawTextProgressive(TextId::kIntroText2);
if (!aborted) {
_engine->_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 19, 20));
aborted |= _engine->_text->drawTextProgressive(TextId::kIntroText3);
}
}
_engine->_cfgfile.FlagDisplayText = tmpFlagDisplayText;
_engine->_screens->fadeToBlack(_engine->_screens->_palettePcx);
_engine->_screens->clearScreen();
if (!aborted) {
_engine->_music->playMidiFile(1);
_engine->_movie->playMovie(FLA_INTROD);
}
_engine->_text->normalWinDial();
} else {
_engine->_movie->playMovie(ACF_INTRO);
}
_engine->_screens->clearScreen();
_engine->_text->_flagMessageShade = true;
_engine->_text->_renderTextTriangle = false;
// set main palette back
_engine->setPalette(_engine->_screens->_ptrPal);
}
// TODO: dotemu has credits_<lang>.txt files
void MenuOptions::showCredits() {
const int32 tmpShadowMode = _engine->_cfgfile.ShadowMode;
_engine->_cfgfile.ShadowMode = 0;
_engine->_gameState->initEngineVars();
_engine->_scene->_numCube = LBA1SceneId::Credits_List_Sequence;
_engine->_scene->_newCube = LBA1SceneId::Credits_List_Sequence;
_engine->_screens->clearScreen();
flagCredits = true;
_engine->mainLoop();
_engine->_scene->stopRunningGame();
flagCredits = false;
_engine->_cfgfile.ShadowMode = tmpShadowMode;
_engine->_screens->clearScreen();
_engine->_input->enableKeyMap(uiKeyMapId);
}
void MenuOptions::showEndSequence() {
_engine->_movie->playMovie(FLA_THEEND);
_engine->_screens->clearScreen();
_engine->setPalette(_engine->_screens->_ptrPal);
}
void MenuOptions::drawSelectableCharacter(int32 x, int32 y) {
const int32 borderTop = 200;
const int32 borderLeft = _engine->width() / 2 - 295;
const int32 halfButtonHeight = 25;
const int32 halfButtonWidth = 20;
const int32 buttonDistanceX = halfButtonWidth * 2 + 5;
const int32 buttonDistanceY = halfButtonHeight * 2 + 5;
const int32 centerX = x * buttonDistanceX + borderLeft;
const int32 centerY = y * buttonDistanceY + borderTop;
const Common::Rect rect(centerX - halfButtonWidth, centerY - halfButtonHeight, centerX + halfButtonWidth, centerY + halfButtonHeight);
if (_engine->_input->isMouseHovering(rect)) {
setOnScreenKeyboard(x, y);
}
const int idx = x + y * ONSCREENKEYBOARD_WIDTH;
if (_onScreenKeyboardDirty[idx] == 0) {
return;
}
--_onScreenKeyboardDirty[idx];
const char buffer[]{allowedCharIndex[idx], '\0'};
const bool selected = _onScreenKeyboardX == x && _onScreenKeyboardY == y;
if (selected) {
_engine->_interface->box(rect, COLOR_91);
} else {
_engine->blitWorkToFront(rect);
_engine->_interface->shadeBox(rect, 4);
}
_engine->_menu->drawRectBorders(rect);
_engine->_text->setFontColor(COLOR_WHITE);
const uint8 character = (uint8)allowedCharIndex[idx];
const int32 textX = centerX - _engine->_text->getCharWidth(character) / 2;
const int32 textY = centerY - _engine->_text->getCharHeight(character) / 2;
_engine->_text->drawText(textX, textY, buffer);
}
void MenuOptions::setOnScreenKeyboard(int x, int y) {
if (x < 0) {
x = ONSCREENKEYBOARD_WIDTH - 1;
} else if (x >= ONSCREENKEYBOARD_WIDTH) {
x = 0;
}
if (y < 0) {
y = ONSCREENKEYBOARD_HEIGHT - 1;
} else if (y >= ONSCREENKEYBOARD_HEIGHT) {
y = 0;
}
if (_onScreenKeyboardX == x && _onScreenKeyboardY == y) {
return;
}
++_onScreenKeyboardDirty[_onScreenKeyboardX + _onScreenKeyboardY * ONSCREENKEYBOARD_WIDTH];
++_onScreenKeyboardDirty[x + y * ONSCREENKEYBOARD_WIDTH];
_onScreenKeyboardX = x;
_onScreenKeyboardY = y;
_onScreenKeyboardLeaveViaOkButton = true;
}
void MenuOptions::drawSelectableCharacters() {
for (int8 x = 0; x < ONSCREENKEYBOARD_WIDTH; x++) {
for (int8 y = 0; y < ONSCREENKEYBOARD_HEIGHT; y++) {
drawSelectableCharacter(x, y);
}
}
}
void MenuOptions::drawInputText(int32 centerx, int32 top, int32 type, const char *text) {
const int32 left = 10;
const int right = _engine->width() - left;
const int bottom = top + PLASMA_HEIGHT;
const Common::Rect rect(left, top, right, bottom);
if (type == 1) {
_engine->_menu->processPlasmaEffect(rect, 32);
}
Common::Rect rectBox(rect);
rectBox.grow(-1);
_engine->_menu->drawRectBorders(rect);
_engine->_interface->shadeBox(rectBox, 3);
_engine->_text->drawText(centerx - _engine->_text->sizeFont(text) / 2, top + 6, text);
_engine->copyBlockPhys(rect);
}
/**
* @brief Toggle a given @c OSystem::Feature and restore the previous state on destruction
*/
class ScopedFeatureState {
private:
OSystem::Feature _feature;
bool _changeTo;
public:
ScopedFeatureState(OSystem::Feature feature, bool enable) : _feature(feature), _changeTo(enable) {
if (g_system->getFeatureState(feature) != enable) {
g_system->setFeatureState(feature, enable);
_changeTo = !g_system->getFeatureState(feature);
}
}
~ScopedFeatureState() {
g_system->setFeatureState(_feature, _changeTo);
}
};
bool MenuOptions::enterText(TextId textIdx, char *textTargetBuf, size_t bufSize) {
_engine->_text->initDial(TextBankId::Options_and_menus);
char buffer[256];
_engine->_text->getMenuText(textIdx, buffer, sizeof(buffer));
_engine->_text->setFontColor(COLOR_WHITE);
const int halfScreenWidth = (_engine->width() / 2);
_engine->_text->drawText(halfScreenWidth - (_engine->_text->sizeFont(buffer) / 2), 20, buffer);
_engine->copyBlockPhys(0, 0, _engine->width() - 1, 99);
Common::fill(&_onScreenKeyboardDirty[0], &_onScreenKeyboardDirty[ARRAYSIZE(_onScreenKeyboardDirty)], 1);
ScopedFeatureState scopedVirtualKeyboard(OSystem::kFeatureVirtualKeyboard, true);
for (;;) {
FrameMarker frame(_engine);
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
_engine->_input->processCustomEngineEventEnd(event);
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
_engine->_input->processCustomEngineEventStart(event);
if (_engine->_input->toggleActionIfActive(TwinEActionType::UIEnter)) {
if (_onScreenKeyboardLeaveViaOkButton) {
if (_onScreenKeyboardX == ONSCREENKEYBOARD_WIDTH - 1 && _onScreenKeyboardY == ONSCREENKEYBOARD_HEIGHT - 1) {
if (textTargetBuf[0] == '\0') {
continue;
}
return true;
}
const size_t size = strlen(textTargetBuf);
if (_onScreenKeyboardX == ONSCREENKEYBOARD_WIDTH - 2 && _onScreenKeyboardY == ONSCREENKEYBOARD_HEIGHT - 1) {
if (size >= 1) {
textTargetBuf[size - 1] = '\0';
}
continue;
}
const char chr = allowedCharIndex[_onScreenKeyboardX + _onScreenKeyboardY * ONSCREENKEYBOARD_WIDTH];
textTargetBuf[size] = chr;
textTargetBuf[size + 1] = '\0';
if (size + 1 >= bufSize - 1) {
return true;
}
continue;
}
if (textTargetBuf[0] == '\0') {
continue;
}
return true;
}
if (_engine->_input->toggleActionIfActive(TwinEActionType::UIAbort)) {
return false;
}
if (_engine->_input->toggleActionIfActive(TwinEActionType::UILeft)) {
setOnScreenKeyboard(_onScreenKeyboardX - 1, _onScreenKeyboardY);
} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIRight)) {
setOnScreenKeyboard(_onScreenKeyboardX + 1, _onScreenKeyboardY);
}
if (_engine->_input->toggleActionIfActive(TwinEActionType::UIUp)) {
setOnScreenKeyboard(_onScreenKeyboardX, _onScreenKeyboardY - 1);
} else if (_engine->_input->toggleActionIfActive(TwinEActionType::UIDown)) {
setOnScreenKeyboard(_onScreenKeyboardX, _onScreenKeyboardY + 1);
}
break;
case Common::EVENT_KEYDOWN: {
const size_t size = strlen(textTargetBuf);
if (!Common::isPrint(event.kbd.ascii)) {
if (event.kbd.keycode == Common::KEYCODE_BACKSPACE) {
if (size >= 1) {
textTargetBuf[size - 1] = '\0';
_onScreenKeyboardLeaveViaOkButton = false;
}
}
continue;
}
if (size >= bufSize - 1) {
return true;
}
if (strchr(allowedCharIndex, event.kbd.ascii)) {
textTargetBuf[size] = event.kbd.ascii;
textTargetBuf[size + 1] = '\0';
_onScreenKeyboardLeaveViaOkButton = false;
}
break;
}
default:
break;
}
}
if (_engine->shouldQuit()) {
break;
}
drawInputText(halfScreenWidth, 100, 1, textTargetBuf);
drawSelectableCharacters();
}
return false;
}
bool MenuOptions::newGameMenu() {
_engine->restoreFrontBuffer();
_saveGameName[0] = '\0';
if (!enterText(TextId::kEnterYourName, _saveGameName, sizeof(_saveGameName))) {
return false;
}
_engine->_gameState->initEngineVars();
newGame();
return true;
}
int MenuOptions::chooseSave(TextId textIdx, bool showEmptySlots) {
const SaveStateList &savegames = _engine->getSaveSlots();
if (savegames.empty() && !showEmptySlots) {
return -1;
}
_engine->_text->initDial(TextBankId::Options_and_menus);
MenuSettings saveFiles;
saveFiles.addButton(TextId::kReturnMenu);
const int maxButtons = _engine->getMetaEngine()->getMaximumSaveSlot() + 1;
uint savesIndex = 0;
for (int i = 1; i < maxButtons; ++i) {
if (savesIndex < savegames.size()) {
const SaveStateDescriptor &savegame = savegames[savesIndex];
if (savegame.getSaveSlot() == i - 1) {
// manually creating a savegame should not overwrite the autosave slot
if (textIdx != TextId::kCreateSaveGame || i > 1) {
saveFiles.addButton(savegame.getDescription().encode().c_str(), i);
}
++savesIndex;
} else if (showEmptySlots) {
saveFiles.addButton("EMPTY", i);
}
} else if (showEmptySlots) {
saveFiles.addButton("EMPTY", i);
}
}
const int32 id = _engine->_menu->doGameMenu(&saveFiles);
switch (id) {
case kQuitEngine:
case (int32)TextId::kReturnMenu:
return -1;
default:
const int slot = saveFiles.getButtonState(id) - 1;
debug("Selected savegame slot %d", slot);
return slot;
}
return -1;
}
bool MenuOptions::continueGameMenu() {
_engine->restoreFrontBuffer();
const int slot = chooseSave(TextId::kContinueGame);
if (slot >= 0) {
debug("Load slot %i", slot);
Common::Error state = _engine->loadGameState(slot);
if (state.getCode() != Common::kNoError) {
error("Failed to load slot %i", slot);
return false;
}
return true;
}
return false;
}
bool MenuOptions::deleteSaveMenu() {
_engine->restoreFrontBuffer();
const int slot = chooseSave(TextId::kDeleteSaveGame);
if (slot >= 0) {
_engine->wipeSaveSlot(slot);
return true;
}
return false;
}
bool MenuOptions::saveGameMenu() {
if (!_engine->_scene->isGameRunning()) {
return false;
}
_engine->restoreFrontBuffer();
const int slot = chooseSave(TextId::kCreateSaveGame, true);
if (slot >= 0) {
char buf[30];
strncpy(buf, _engine->_gameState->_sceneName, sizeof(buf));
buf[sizeof(buf) - 1] = '\0';
_engine->restoreFrontBuffer();
enterText(TextId::kEnterYourNewName, buf, sizeof(buf));
// may not be empty
if (buf[0] == '\0') {
Common::strlcpy(buf, _engine->_gameState->_sceneName, sizeof(buf));
}
Common::Error state = _engine->saveGameState(slot, buf, false);
if (state.getCode() != Common::kNoError) {
error("Failed to save slot %i", slot);
return false;
}
return true;
}
return false;
}
} // namespace TwinE

View File

@@ -0,0 +1,73 @@
/* 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 TWINE_MENUOPTIONS_H
#define TWINE_MENUOPTIONS_H
#include "common/rect.h"
#define ONSCREENKEYBOARD_WIDTH 14
#define ONSCREENKEYBOARD_HEIGHT 5
#include "common/scummsys.h"
#include "twine/scene/actor.h"
namespace TwinE {
class MenuOptions {
private:
TwinEEngine *_engine;
uint8 _onScreenKeyboardDirty[ONSCREENKEYBOARD_WIDTH * ONSCREENKEYBOARD_HEIGHT] { 0 };
int _onScreenKeyboardX = 0;
int _onScreenKeyboardY = 0;
bool _onScreenKeyboardLeaveViaOkButton = false;
void setOnScreenKeyboard(int x, int y);
bool enterText(TextId textIdx, char *textTargetBuf, size_t bufSize);
void drawSelectableCharacters();
void drawInputText(int32 centerx, int32 top, int32 type, const char *text);
void drawSelectableCharacter(int32 x, int32 y);
int chooseSave(TextId textIdx, bool showEmptySlots = false);
public:
MenuOptions(TwinEEngine *engine) : _engine(engine) {}
void showEndSequence();
void showCredits();
bool flagCredits = false;
char _saveGameName[32] {'\0'};
/** Main menu new game options */
bool newGameMenu();
void newGame();
/** Main menu continue game options */
bool continueGameMenu();
bool saveGameMenu();
bool deleteSaveMenu();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,549 @@
/* 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 "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "base/plugins.h"
#include "common/system.h"
#include "common/translation.h"
#include "engines/advancedDetector.h"
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
#include "graphics/scaler.h"
#include "twine/achievements_tables.h"
#include "twine/detection.h"
#include "twine/input.h"
#include "twine/renderer/screens.h"
#include "twine/twine.h"
namespace TwinE {
static const ADExtraGuiOptionsMap twineOptionsList[] = {
{
GAMEOPTION_WALL_COLLISION,
{
_s("Enable wall collisions"),
_s("Enable the original wall collision damage"),
"wallcollision",
false,
0,
0
}
},
{
// this only changes the menu and doesn't change the autosave behaviour - as scummvm is handling this now
GAMEOPTION_DISABLE_SAVE_MENU,
{
_s("Disable save menu"),
_s("The original only had autosaves. This allows you to save whenever you want."),
"useautosaving",
false,
0,
0
}
},
{
GAMEOPTION_DEBUG,
{
_s("Enable debug mode"),
_s("Enable the debug mode"),
"debug",
false,
0,
0
}
},
{
GAMEOPTION_SOUND,
{
_s("Enable sound"),
_s("Enable the sound for the game"),
"sound",
true,
0,
0
}
},
{
GAMEOPTION_TEXT,
{
_s("Enable text"),
_s("Enable the text for the game"),
"subtitles",
true,
0,
0
}
},
{
GAMEOPTION_MOVIES,
{
_s("Enable movies"),
_s("Enable the cutscenes for the game"),
"movie",
true,
0,
0
}
},
{
GAMEOPTION_MOUSE,
{
_s("Enable mouse"),
_s("Enable the mouse for the UI"),
"mouse",
true,
0,
0
}
},
{
GAMEOPTION_USA_VERSION,
{
_s("Use the USA version"),
_s("Enable the USA specific version flags"),
"version",
false,
0,
0
}
},
{
GAMEOPTION_HIGH_RESOLUTION,
{
_s("Enable high resolution"),
_s("Enable a higher resolution for the game"),
"usehighres",
false,
0,
0
}
},
#ifdef USE_TTS
{
GAMEOPTION_TEXT_TO_SPEECH,
{
_s("TTS Narrator"),
_s("Use TTS to read the descriptions (if TTS is available)"),
"tts_narrator",
false,
0,
0
}
},
#endif
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
class TwinEMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override {
return "twine";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return twineOptionsList;
}
int getMaximumSaveSlot() const override {
return 6;
}
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override {
TwineGameType gameType = TwineGameType::GType_LBA;
const Common::String gameId = desc->gameId;
if (gameId == "lba") {
gameType = TwineGameType::GType_LBA;
} else if (gameId == "lba2") {
gameType = TwineGameType::GType_LBA2;
} else if (gameId == "lbashow") {
gameType = TwineGameType::GType_LBASHOW;
}
*engine = new TwinE::TwinEEngine(syst, desc->language, desc->flags, desc->platform, gameType);
return Common::kNoError;
}
Common::Array<Common::Keymap *> initKeymaps(const char *target) const override;
const Common::AchievementDescriptionList *getAchievementDescriptionList() const override {
return TwinE::achievementDescriptionList;
}
void getSavegameThumbnail(Graphics::Surface &thumb) override;
};
void TwinEMetaEngine::getSavegameThumbnail(Graphics::Surface &thumb) {
TwinEEngine *engine = (TwinEEngine *)g_engine;
const Graphics::ManagedSurface &managedSurface = engine->_workVideoBuffer;
const Graphics::Surface &screenSurface = managedSurface.rawSurface();
Graphics::Palette *pal = &engine->_screens->_ptrPal;
if (engine->_screens->_flagPalettePcx) {
pal = &engine->_screens->_palettePcx;
}
::createThumbnail(&thumb, (const uint8 *)screenSurface.getPixels(), screenSurface.w, screenSurface.h, pal->data());
}
//
// unused:
// JOY_LEFT_TRIGGER
// JOY_RIGHT_TRIGGER
// JOY_RIGHT_SHOULDER
//
// JOY_RIGHT_STICK_X
// JOY_RIGHT_STICK_Y
// JOY_LEFT_STICK_X
// JOY_LEFT_STICK_Y
//
Common::KeymapArray TwinEMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
Action *act;
KeymapArray array(4);
{
Keymap *gameKeyMap = new Keymap(Keymap::kKeymapTypeGame, mainKeyMapId, "Little Big Adventure");
act = new Action("PAUSE", _("Pause"));
act->setCustomEngineActionEvent(TwinEActionType::Pause);
act->addDefaultInputMapping("p");
gameKeyMap->addAction(act);
act = new Action("DEBUGGRIDCAMERAPRESSUP", _("Debug grid camera up"));
act->setCustomEngineActionEvent(TwinEActionType::DebugGridCameraPressUp);
act->addDefaultInputMapping("s");
gameKeyMap->addAction(act);
act = new Action("DEBUGGRIDCAMERAPRESSDOWN", _("Debug grid camera down"));
act->setCustomEngineActionEvent(TwinEActionType::DebugGridCameraPressDown);
act->addDefaultInputMapping("x");
gameKeyMap->addAction(act);
act = new Action("DEBUGGRIDCAMERAPRESSLEFT", _("Debug grid camera left"));
act->setCustomEngineActionEvent(TwinEActionType::DebugGridCameraPressLeft);
act->addDefaultInputMapping("y");
act->addDefaultInputMapping("z");
gameKeyMap->addAction(act);
act = new Action("DEBUGGRIDCAMERAPRESSRIGHT", _("Debug grid camera right"));
act->setCustomEngineActionEvent(TwinEActionType::DebugGridCameraPressRight);
act->addDefaultInputMapping("c");
gameKeyMap->addAction(act);
act = new Action("CHANGETONORMALBEHAVIOUR", _("Normal behavior"));
act->setCustomEngineActionEvent(TwinEActionType::ChangeBehaviourNormal);
act->addDefaultInputMapping("1");
gameKeyMap->addAction(act);
act = new Action("CHANGETOATHLETICBEHAVIOUR", _("Athletic behavior"));
act->setCustomEngineActionEvent(TwinEActionType::ChangeBehaviourAthletic);
act->addDefaultInputMapping("2");
gameKeyMap->addAction(act);
act = new Action("CHANGETOAGGRESSIVEBEHAVIOUR", _("Aggressive behavior"));
act->setCustomEngineActionEvent(TwinEActionType::ChangeBehaviourAggressive);
act->addDefaultInputMapping("3");
gameKeyMap->addAction(act);
act = new Action("CHANGETODISCREETBEHAVIOUR", _("Discreet behavior"));
act->setCustomEngineActionEvent(TwinEActionType::ChangeBehaviourDiscreet);
act->addDefaultInputMapping("4");
gameKeyMap->addAction(act);
act = new Action("NORMALBEHAVIOUR", _("Normal behavior"));
act->setCustomEngineActionEvent(TwinEActionType::QuickBehaviourNormal);
act->addDefaultInputMapping("F1");
gameKeyMap->addAction(act);
act = new Action("ATHLETICBEHAVIOUR", _("Athletic behavior"));
act->setCustomEngineActionEvent(TwinEActionType::QuickBehaviourAthletic);
act->addDefaultInputMapping("F2");
gameKeyMap->addAction(act);
act = new Action("AGGRESSIVEBEHAVIOUR", _("Aggressive behavior"));
act->setCustomEngineActionEvent(TwinEActionType::QuickBehaviourAggressive);
act->addDefaultInputMapping("F3");
gameKeyMap->addAction(act);
act = new Action("DISCREETBEHAVIOUR", _("Discreet behavior"));
act->setCustomEngineActionEvent(TwinEActionType::QuickBehaviourDiscreet);
act->addDefaultInputMapping("F4");
gameKeyMap->addAction(act);
act = new Action("BEHAVIOURACTION", _("Behavior action"));
act->setCustomEngineActionEvent(TwinEActionType::ExecuteBehaviourAction);
act->addDefaultInputMapping("SPACE");
act->addDefaultInputMapping("JOY_A");
gameKeyMap->addAction(act);
act = new Action("CHANGEBEHAVIOUR", _("Change behavior"));
act->setCustomEngineActionEvent(TwinEActionType::BehaviourMenu);
act->addDefaultInputMapping("LCTRL");
act->addDefaultInputMapping("RCTRL");
act->addDefaultInputMapping("JOY_X");
gameKeyMap->addAction(act);
act = new Action("MENU", _("Global Main Menu"));
act->addDefaultInputMapping("F5");
act->setEvent(EVENT_MAINMENU);
gameKeyMap->addAction(act);
act = new Action("OPTIONSMENU", _("Options menu"));
act->setCustomEngineActionEvent(TwinEActionType::OptionsMenu);
act->addDefaultInputMapping("F6");
gameKeyMap->addAction(act);
act = new Action("CENTER", _("Center"));
act->setCustomEngineActionEvent(TwinEActionType::RecenterScreenOnTwinsen);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("JOY_RIGHT_STICK");
gameKeyMap->addAction(act);
act = new Action("USESELECTEDOBJECT", _("Use selected object"));
act->setCustomEngineActionEvent(TwinEActionType::UseSelectedObject);
act->addDefaultInputMapping("S+RETURN");
act->addDefaultInputMapping("S+KP_ENTER");
gameKeyMap->addAction(act);
act = new Action("THROWMAGICBALL", _("Throw magic ball"));
act->setCustomEngineActionEvent(TwinEActionType::ThrowMagicBall);
act->addDefaultInputMapping("LALT");
act->addDefaultInputMapping("RALT");
act->addDefaultInputMapping("JOY_LEFT_STICK");
gameKeyMap->addAction(act);
act = new Action("MOVEFORWARD", _("Move forward"));
act->setCustomEngineActionEvent(TwinEActionType::MoveForward);
act->addDefaultInputMapping("UP");
act->addDefaultInputMapping("KP8");
act->addDefaultInputMapping("JOY_UP");
gameKeyMap->addAction(act);
act = new Action("MOVEBACKWARD", _("Move backwards"));
act->setCustomEngineActionEvent(TwinEActionType::MoveBackward);
act->addDefaultInputMapping("DOWN");
act->addDefaultInputMapping("KP2");
act->addDefaultInputMapping("JOY_DOWN");
gameKeyMap->addAction(act);
act = new Action("TURNRIGHT", _("Turn right"));
act->setCustomEngineActionEvent(TwinEActionType::TurnRight);
act->addDefaultInputMapping("RIGHT");
act->addDefaultInputMapping("KP6");
act->addDefaultInputMapping("JOY_RIGHT");
gameKeyMap->addAction(act);
act = new Action("TURNLEFT", _("Turn left"));
act->setCustomEngineActionEvent(TwinEActionType::TurnLeft);
act->addDefaultInputMapping("LEFT");
act->addDefaultInputMapping("KP4");
act->addDefaultInputMapping("JOY_LEFT");
gameKeyMap->addAction(act);
act = new Action("USEPROTOPACK", _("Use Protopack"));
act->setCustomEngineActionEvent(TwinEActionType::UseProtoPack);
act->addDefaultInputMapping("j");
gameKeyMap->addAction(act);
act = new Action("OPENHOLOMAP", _("Open Holomap"));
act->setCustomEngineActionEvent(TwinEActionType::OpenHolomap);
act->addDefaultInputMapping("h");
gameKeyMap->addAction(act);
act = new Action("INVENTORY", _("Inventory"));
act->setCustomEngineActionEvent(TwinEActionType::InventoryMenu);
act->addDefaultInputMapping("LSHIFT");
act->addDefaultInputMapping("RSHIFT");
act->addDefaultInputMapping("JOY_Y");
act->addDefaultInputMapping("i");
gameKeyMap->addAction(act);
act = new Action("SPECIALACTION", _("Special action"));
act->setCustomEngineActionEvent(TwinEActionType::SpecialAction);
act->addDefaultInputMapping("w");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
gameKeyMap->addAction(act);
act = new Action("SCENERYZOOM", _("Scenery zoom"));
act->setCustomEngineActionEvent(TwinEActionType::SceneryZoom);
gameKeyMap->addAction(act);
act = new Action("ESCAPE", _("Escape"));
act->setCustomEngineActionEvent(TwinEActionType::Escape);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("JOY_BACK");
gameKeyMap->addAction(act);
// TODO: lba2 has shortcuts for the inventory items
// J: Protopack/Jetpack
// P: Mecha-Penguin
// H: Holomap
// X: Dodges
// 1: Magic Ball
// 2: Darts
// 3: Blowpipe/Blowtron
// 4: Conch Shell
// 5: Glove
// 6: Laser Gun
// 7: Saber
array[0] = gameKeyMap;
}
{
Keymap *uiKeyMap = new Keymap(Keymap::kKeymapTypeGame, uiKeyMapId, "Little Big Adventure UI");
act = new Action("ACCEPT", _("Accept"));
act->setCustomEngineActionEvent(TwinEActionType::UIEnter);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
uiKeyMap->addAction(act);
act = new Action("ABORT", _("Abort"));
act->setCustomEngineActionEvent(TwinEActionType::UIAbort);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_BACK");
act->addDefaultInputMapping("JOY_B");
uiKeyMap->addAction(act);
act = new Action("UP", _("Up"));
act->setCustomEngineActionEvent(TwinEActionType::UIUp);
act->addDefaultInputMapping("UP");
act->addDefaultInputMapping("KP8");
act->addDefaultInputMapping("JOY_UP");
uiKeyMap->addAction(act);
act = new Action("DOWN", _("Down"));
act->setCustomEngineActionEvent(TwinEActionType::UIDown);
act->addDefaultInputMapping("DOWN");
act->addDefaultInputMapping("KP2");
act->addDefaultInputMapping("JOY_DOWN");
uiKeyMap->addAction(act);
act = new Action("RIGHT", _("Right"));
act->setCustomEngineActionEvent(TwinEActionType::UIRight);
act->addDefaultInputMapping("RIGHT");
act->addDefaultInputMapping("KP6");
act->addDefaultInputMapping("MOUSE_WHEEL_UP");
act->addDefaultInputMapping("JOY_RIGHT");
uiKeyMap->addAction(act);
act = new Action("LEFT", _("Left"));
act->setCustomEngineActionEvent(TwinEActionType::UILeft);
act->addDefaultInputMapping("LEFT");
act->addDefaultInputMapping("KP4");
act->addDefaultInputMapping("MOUSE_WHEEL_DOWN");
act->addDefaultInputMapping("JOY_LEFT");
uiKeyMap->addAction(act);
act = new Action("NEXTPAGE", _("Next page"));
act->setCustomEngineActionEvent(TwinEActionType::UINextPage);
act->addDefaultInputMapping("SPACE");
act->addDefaultInputMapping("PAGEDOWN");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("JOY_BACK");
uiKeyMap->addAction(act);
array[1] = uiKeyMap;
}
{
Keymap *cutsceneKeyMap = new Keymap(Keymap::kKeymapTypeGame, cutsceneKeyMapId, "Little Big Adventure Cutscenes");
act = new Action("ABORT", _("Abort"));
act->setCustomEngineActionEvent(TwinEActionType::CutsceneAbort);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("SPACE");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("JOY_BACK");
cutsceneKeyMap->addAction(act);
array[2] = cutsceneKeyMap;
}
{
Keymap *holomapKeyMap = new Keymap(Keymap::kKeymapTypeGame, holomapKeyMapId, "Little Big Adventure Holomap");
act = new Action("ABORT", _("Abort"));
act->setCustomEngineActionEvent(TwinEActionType::HolomapAbort);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("JOY_BACK");
holomapKeyMap->addAction(act);
act = new Action("UP", _("Up"));
act->setCustomEngineActionEvent(TwinEActionType::HolomapUp);
act->addDefaultInputMapping("C+UP");
act->addDefaultInputMapping("KP8");
act->addDefaultInputMapping("MOUSE_WHEEL_UP");
act->addDefaultInputMapping("JOY_UP");
holomapKeyMap->addAction(act);
act = new Action("DOWN", _("Down"));
act->setCustomEngineActionEvent(TwinEActionType::HolomapDown);
act->addDefaultInputMapping("C+DOWN");
act->addDefaultInputMapping("KP2");
act->addDefaultInputMapping("MOUSE_WHEEL_DOWN");
act->addDefaultInputMapping("JOY_DOWN");
holomapKeyMap->addAction(act);
act = new Action("RIGHT", _("Right"));
act->setCustomEngineActionEvent(TwinEActionType::HolomapRight);
act->addDefaultInputMapping("C+RIGHT");
act->addDefaultInputMapping("KP6");
act->addDefaultInputMapping("JOY_RIGHT");
holomapKeyMap->addAction(act);
act = new Action("LEFT", _("Left"));
act->setCustomEngineActionEvent(TwinEActionType::HolomapLeft);
act->addDefaultInputMapping("C+LEFT");
act->addDefaultInputMapping("KP4");
act->addDefaultInputMapping("JOY_LEFT");
holomapKeyMap->addAction(act);
act = new Action("PREV", _("Previous location"));
act->setCustomEngineActionEvent(TwinEActionType::HolomapPrev);
act->addDefaultInputMapping("LEFT");
holomapKeyMap->addAction(act);
act = new Action("NEXT", _("Next location"));
act->setCustomEngineActionEvent(TwinEActionType::HolomapNext);
act->addDefaultInputMapping("RIGHT");
holomapKeyMap->addAction(act);
array[3] = holomapKeyMap;
}
return array;
}
} // namespace TwinE
#if PLUGIN_ENABLED_DYNAMIC(TWINE)
REGISTER_PLUGIN_DYNAMIC(TWINE, PLUGIN_TYPE_ENGINE, TwinE::TwinEMetaEngine);
#else
REGISTER_PLUGIN_STATIC(TWINE, PLUGIN_TYPE_ENGINE, TwinE::TwinEMetaEngine);
#endif

76
engines/twine/module.mk Normal file
View File

@@ -0,0 +1,76 @@
MODULE := engines/twine
MODULE_OBJS := \
audio/music.o \
audio/sound.o \
\
debugger/console.o \
debugger/debug_state.o \
\
menu/interface.o \
menu/menu.o \
menu/menuoptions.o \
\
parser/anim.o \
parser/anim3ds.o \
parser/blocklibrary.o \
parser/body.o \
parser/entity.o \
parser/holomap.o \
parser/parser.o \
parser/sprite.o \
parser/text.o \
\
renderer/redraw.o \
renderer/renderer.o \
renderer/screens.o \
\
scene/actor.o \
scene/animations.o \
scene/buggy.o \
scene/collision.o \
scene/dart.o \
scene/extra.o \
scene/gamestate.o \
scene/grid.o \
scene/movements.o \
scene/rain.o \
scene/scene.o \
scene/wagon.o \
\
script/script_life.o \
script/script_move.o \
script/script_life_v1.o \
script/script_life_v2.o \
script/script_move_v1.o \
script/script_move_v2.o \
\
resources/hqr.o \
resources/lzss.o \
resources/resources.o \
\
movies.o \
holomap_v1.o \
holomap_v2.o \
input.o \
metaengine.o \
shared.o \
slideshow.o \
text.o \
twine.o
ifdef USE_IMGUI
MODULE_OBJS += \
debugger/debugtools.o
endif
# This module can be built as a plugin
ifeq ($(ENABLE_TWINE), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

564
engines/twine/movies.cpp Normal file
View File

@@ -0,0 +1,564 @@
/* 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 "twine/movies.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/file.h"
#include "common/language.h"
#include "common/str.h"
#include "common/system.h"
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
#include "image/gif.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/scene/grid.h"
#include "twine/shared.h"
#include "twine/twine.h"
#include "video/smk_decoder.h"
namespace TwinE {
/** FLA Frame Opcode types */
enum FlaFrameOpcode {
kLoadPalette = 1,
kInfo = 2,
kPlaySample = 3,
kSampleBalance = 4,
kStopSample = 5,
kDeltaFrame = 6,
kBlackFrame = 7,
kBrownFrame = 8,
kCopy = 9,
kFliCopy = 16
};
/** FLA movie sample structure */
struct FLASampleStruct {
/** Number os samples */
int16 sampleNum = 0;
/** Sample frequency */
int16 freq = 0;
/** Numbers of time to repeat */
int16 repeat = 0;
uint8 balance = 0;
uint8 volumeLeft = 0;
uint8 volumeRight = 0;
};
/** FLA movie extension */
#define FLA_EXT ".fla"
void Movies::drawKeyFrame(Common::MemoryReadStream &stream, int32 width, int32 height) {
uint8 *destPtr = (uint8 *)_flaBuffer;
uint8 *startOfLine = destPtr;
for (int32 y = 0; y < height; ++y) {
const int8 lineEntryCount = stream.readByte();
for (int8 a = 0; a < lineEntryCount; a++) {
const int8 rleFlag = stream.readByte();
if (rleFlag < 0) {
const int8 rleCnt = ABS(rleFlag);
for (int8 b = 0; b < rleCnt; ++b) {
*destPtr++ = stream.readByte();
}
} else {
const char colorFill = stream.readByte();
Common::fill(&destPtr[0], &destPtr[rleFlag], colorFill);
destPtr += rleFlag;
}
}
startOfLine = destPtr = startOfLine + width;
}
}
void Movies::drawDeltaFrame(Common::MemoryReadStream &stream, int32 width) {
const uint16 skip = stream.readUint16LE() * width;
const int32 height = stream.readSint16LE();
uint8 *destPtr = (uint8 *)_flaBuffer + skip;
uint8 *startOfLine = destPtr;
for (int32 y = 0; y < height; ++y) {
const int8 lineEntryCount = stream.readByte();
for (int8 a = 0; a < lineEntryCount; ++a) {
destPtr += stream.readByte();
const int8 rleFlag = stream.readByte();
if (rleFlag > 0) {
for (int8 b = 0; b < rleFlag; ++b) {
*destPtr++ = stream.readByte();
}
} else {
const char colorFill = stream.readByte();
const int8 rleCnt = ABS(rleFlag);
Common::fill(&destPtr[0], &destPtr[rleCnt], colorFill);
destPtr += rleCnt;
}
}
startOfLine = destPtr = startOfLine + width;
}
}
void Movies::scaleFla2x() {
uint8 *source = (uint8 *)_flaBuffer;
uint8 *dest = (uint8 *)_engine->_imageBuffer.getPixels();
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAWIDE) {
Common::fill(&dest[0], &dest[_engine->_imageBuffer.w * 40], 0x00);
dest += _engine->_imageBuffer.w * 40;
}
for (int32 i = 0; i < FLASCREEN_HEIGHT; i++) {
for (int32 j = 0; j < FLASCREEN_WIDTH; j++) {
*dest++ = *source;
*dest++ = *source++;
}
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAWIDE) { // include wide bars
memcpy(dest, dest - _engine->_imageBuffer.w, FLASCREEN_WIDTH * 2);
dest += FLASCREEN_WIDTH * 2;
} else { // stretch the movie like original game.
if (i % 2) {
memcpy(dest, dest - _engine->_imageBuffer.w, FLASCREEN_WIDTH * 2);
dest += FLASCREEN_WIDTH * 2;
}
if (i % 10) {
memcpy(dest, dest - _engine->_imageBuffer.w, FLASCREEN_WIDTH * 2);
dest += FLASCREEN_WIDTH * 2;
}
}
}
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAWIDE) {
Common::fill(&dest[0], &dest[_engine->_imageBuffer.w * 40], 0x00);
}
}
void Movies::drawNextFrameFla() {
FLASampleStruct sample;
_frameData.nbFrames = _file.readSint16LE();
_frameData.offsetNextFrame = _file.readSint32LE();
if (_frameData.offsetNextFrame > _engine->_imageBuffer.w * _engine->_imageBuffer.h) {
warning("Skipping video frame - it would exceed the screen buffer: %i", _frameData.offsetNextFrame);
return;
}
uint8 *outBuf = (uint8 *)_engine->_imageBuffer.getPixels();
_file.read(outBuf, _frameData.offsetNextFrame);
if ((int32)_frameData.nbFrames <= 0) {
return;
}
Common::MemoryReadStream stream(outBuf, _frameData.offsetNextFrame);
for (int32 frame = 0; frame < _frameData.nbFrames; ++frame) {
const uint16 opcode = stream.readUint16LE();
const uint16 opcodeBlockSize = stream.readUint16LE();
const int64 pos = stream.pos();
switch (opcode) {
case kLoadPalette: {
int16 numOfColor = stream.readSint16LE();
int16 startColor = stream.readSint16LE();
if (_paletteOrg.size() < (uint)(numOfColor + startColor)) {
Graphics::Palette palette(numOfColor + startColor);
palette.set(_paletteOrg, 0, _paletteOrg.size());
_paletteOrg = palette;
}
for (int16 i = 0; i < numOfColor; ++i) {
const byte r = stream.readByte();
const byte g = stream.readByte();
const byte b = stream.readByte();
_paletteOrg.set(i + startColor, r, g, b);
}
break;
}
case kInfo: {
int16 innerOpcpde = stream.readSint16LE();
switch (innerOpcpde) {
case 1: // fla flute
_engine->_music->playMidiFile(26);
break;
case 2:
// FLA movies don't use cross fade
// fade out tricky
if (_fadeOut != 1) {
_engine->_screens->fadeToBlack(_paletteOrg);
_fadeOut = 1;
}
break;
case 3:
_flagFirst = true;
break;
case 4:
// TODO: fade out for 1 second before we stop it
_engine->_music->stopMusicMidi();
break;
}
break;
}
case kPlaySample: {
sample.sampleNum = stream.readSint16LE();
sample.freq = stream.readSint16LE();
sample.repeat = stream.readSint16LE();
sample.balance = stream.readByte();
sample.volumeLeft = stream.readByte();
sample.volumeRight = stream.readByte();
_engine->_sound->playFlaSample(sample.sampleNum, sample.freq, sample.repeat, sample.volumeLeft, sample.volumeRight);
break;
}
case kStopSample: {
const int16 sampleNum = stream.readSint16LE();
if (sampleNum == -1) {
_engine->_sound->stopSamples();
} else {
_engine->_sound->stopSample(sampleNum);
}
break;
}
case kDeltaFrame: {
drawDeltaFrame(stream, FLASCREEN_WIDTH);
if (_fadeOut == 1) {
++_fadeOutFrames;
}
break;
}
case kBrownFrame: {
drawKeyFrame(stream, FLASCREEN_WIDTH, _flaHeaderData.ysize);
break;
}
case kBlackFrame: {
const Common::Rect rect(0, 0, FLASCREEN_WIDTH - 1, FLASCREEN_HEIGHT - 1);
_engine->_interface->box(rect, 0);
break;
}
case kCopy:
case kFliCopy: {
const Common::Rect rect(0, 0, 80, 200);
byte *ptr = (byte *)_engine->_frontVideoBuffer.getPixels();
for (int y = rect.top; y < rect.bottom; ++y) {
for (int x = rect.left; x < rect.right; ++x) {
*ptr++ = stream.readByte();
}
ptr = ptr + rect.width();
}
_engine->_frontVideoBuffer.addDirtyRect(rect);
break;
}
case kSampleBalance: {
const int16 sampleNum = stream.readSint16LE();
/* const uint8 offset = */ stream.readByte();
stream.skip(1); // padding
/* const int16 balance = */ stream.readSint16LE();
const uint8 volumeLeft = stream.readByte();
const uint8 volumeRight = stream.readByte();
const int32 channelIdx = _engine->_sound->getSampleChannel(sampleNum);
_engine->_sound->setChannelBalance(channelIdx, volumeLeft, volumeRight);
break;
}
default: {
break;
}
}
stream.seek(pos + opcodeBlockSize);
}
}
Movies::Movies(TwinEEngine *engine) : _engine(engine) {}
#ifdef USE_GIF
void Movies::prepareGIF(int index) {
Image::GIFDecoder decoder;
Common::SeekableReadStream *stream = HQR::makeReadStream(Resources::HQR_FLAGIF_FILE, index);
if (stream == nullptr) {
warning("Failed to load gif hqr entry with id %i from %s", index, Resources::HQR_FLAGIF_FILE);
return;
}
if (!decoder.loadStream(*stream)) {
delete stream;
warning("Failed to load gif with id %i from %s", index, Resources::HQR_FLAGIF_FILE);
return;
}
const Graphics::Surface *surface = decoder.getSurface();
_engine->setPalette(0, decoder.getPalette().size(), decoder.getPalette().data());
Graphics::ManagedSurface& target = _engine->_frontVideoBuffer;
const Common::Rect surfaceBounds(0, 0, surface->w, surface->h);
target.blitFrom(*surface, surfaceBounds, target.getBounds());
debugC(1, TwinE::kDebugMovies, "Show gif with id %i from %s", index, Resources::HQR_FLAGIF_FILE);
delete stream;
_engine->delaySkip(5000);
_engine->setPalette(_engine->_screens->_ptrPal);
}
#endif
void Movies::playGIFMovie(const char *flaName) {
#ifdef USE_GIF
if (!Common::File::exists(Resources::HQR_FLAGIF_FILE)) {
warning("%s file doesn't exist", Resources::HQR_FLAGIF_FILE);
return;
}
Common::String name(flaName);
name.toLowercase();
debugC(1, TwinE::kDebugMovies, "Play gif %s", name.c_str());
// TODO: use the HQR 23th entry (movies informations)
// TODO: there are gifs [1-18]
if (name == FLA_INTROD) {
prepareGIF(3);
prepareGIF(4);
prepareGIF(5);
} else if (name == "bateau" || name == "bateau2") {
prepareGIF(7);
} else if (name == "navette") {
prepareGIF(15);
} else if (name == "templebu") {
prepareGIF(12);
} else if (name == "flute2") {
prepareGIF(8); // TODO: same as glass2?
} else if (name == "glass2") {
prepareGIF(8); // TODO: same as flute2?
} else if (name == "surf") {
prepareGIF(9);
} else if (name == "verser" || name == "verser2") {
prepareGIF(10);
} else if (name == "neige2") {
prepareGIF(11);
} else if (name == "capture") {
prepareGIF(14); // TODO: same as sendel?
} else if (name == "sendel") {
prepareGIF(14); // TODO: same as capture?
} else if (name == "sendel2") {
prepareGIF(17);
} else if (name == FLA_DRAGON3) {
prepareGIF(1);
prepareGIF(2);
} else if (name == "baffe" || name.matchString("baffe#")) {
prepareGIF(6);
} else {
warning("unknown gif image: %s", name.c_str());
}
#else
warning("No GIF support compiled in");
#endif
}
bool Movies::playMovie(const char *name) { // PlayAnimFla
if (_engine->isLBA2()) {
const int index = _engine->_resources->findSmkMovieIndex(name);
if (index == -1) {
return false;
}
return playSmkMovie(name, index);
}
Common::String fileNamePath = name;
const size_t n = fileNamePath.findLastOf(".");
if (n != Common::String::npos) {
fileNamePath.erase(n);
}
if (_engine->_cfgfile.Movie == CONF_MOVIE_FLAGIF) {
playGIFMovie(fileNamePath.c_str());
return true;
}
_fadeOut = -1;
_fadeOutFrames = 0;
_file.close();
if (!_file.open(Common::Path(fileNamePath + FLA_EXT))) {
warning("Failed to open fla movie '%s'", fileNamePath.c_str());
playGIFMovie(fileNamePath.c_str());
_engine->_screens->setBlackPal();
_engine->_screens->clearScreen();
return true;
}
const uint32 version = _file.readUint32BE();
_file.skip(2); // version field is 5 bytes - and one padding byte
_flaHeaderData.numOfFrames = _file.readUint32LE();
_flaHeaderData.speed = _file.readByte();
_file.skip(1); // padding byte
_flaHeaderData.xsize = _file.readUint16LE();
_flaHeaderData.ysize = _file.readUint16LE();
_samplesInFla = (int16)_file.readUint16LE();
/*const uint16 offsetFrame1 =*/ _file.readUint16LE();
// sample number int16
// loop int16
_file.skip(4 * _samplesInFla);
bool finished = false;
if (version == MKTAG('V', '1', '.', '3')) {
int32 currentFrame = 0;
debugC(1, TwinE::kDebugMovies, "Play fla: %s", name);
ScopedKeyMap scopedKeyMap(_engine, cutsceneKeyMapId);
_flagFirst = true;
do {
FrameMarker frame(_engine, _flaHeaderData.speed);
_engine->readKeys();
if (_engine->shouldQuit()) {
break;
}
if (currentFrame == _flaHeaderData.numOfFrames) {
finished = true;
break;
}
drawNextFrameFla();
scaleFla2x();
_engine->_frontVideoBuffer.blitFrom(_engine->_imageBuffer, _engine->_imageBuffer.getBounds(), _engine->_frontVideoBuffer.getBounds());
// Only blit to screen if isn't a fade
if (_fadeOut == -1) {
if (currentFrame == 0) {
// fade in the first frame
_engine->_screens->fadeToPal(_paletteOrg);
} else {
_engine->setPalette(_paletteOrg);
}
}
// TRICKY: fade in tricky
if (_fadeOutFrames >= 2) {
_engine->_screens->fadeToPal(_paletteOrg);
_fadeOut = -1;
_fadeOutFrames = 0;
}
currentFrame++;
} while (!_engine->_input->toggleAbortAction());
} else {
warning("Unsupported fla version: %u, %s", version, fileNamePath.c_str());
}
// this might happen if the movie was interrupted before it even started to load the palette.
if (!_paletteOrg.empty()) {
_engine->_screens->fadeToBlack(_paletteOrg);
}
_engine->_sound->stopSamples();
_engine->_screens->setBlackPal();
_engine->_screens->clearScreen();
return finished;
}
class TwineSmackerDecoder : public Video::SmackerDecoder {
public:
void enableLanguage(int track, int volume) {
AudioTrack* audio = getAudioTrack(track);
if (audio == nullptr) {
return;
}
audio->setMute(false);
audio->setVolume(CLIP<int>(volume, 0, Audio::Mixer::kMaxMixerVolume));
}
};
bool Movies::playSmkMovie(const char *name, int index) {
assert(_engine->isLBA2());
TwineSmackerDecoder decoder;
Common::SeekableReadStream *stream = HQR::makeReadStream(TwineResource(Resources::HQR_VIDEO_FILE, index));
if (stream == nullptr) {
warning("Failed to find smacker video %i", index);
return false;
}
if (!decoder.loadStream(stream)) {
warning("Failed to load smacker video %i", index);
return false;
}
const int volume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
decoder.setVolume(CLIP<int>(volume, 0, Audio::Mixer::kMaxMixerVolume));
decoder.start();
decoder.setAudioTrack(0); // music
if (ConfMan.getInt("audio_language") > 0) {
int additionalAudioTrack = -1;
if (!scumm_strnicmp(name, "INTRO", 5)) {
switch (_engine->getGameLang()) {
default:
case Common::Language::EN_ANY:
case Common::Language::EN_GRB:
case Common::Language::EN_USA:
additionalAudioTrack = 3;
break;
case Common::Language::DE_DEU:
additionalAudioTrack = 2;
break;
case Common::Language::FR_FRA:
additionalAudioTrack = 1;
break;
}
}
const int speechVolume = _engine->_system->getMixer()->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
debugC(1, TwinE::kDebugMovies, "Play additional speech track: %i (of %i tracks)", additionalAudioTrack, decoder.getAudioTrackCount());
decoder.enableLanguage(additionalAudioTrack, speechVolume);
} else {
debugC(1, TwinE::kDebugMovies, "Disabled smacker speech");
}
for (;;) {
if (decoder.endOfVideo()) {
break;
}
FrameMarker frame(_engine);
_engine->_input->readKeys();
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
break;
}
if (decoder.needsUpdate()) {
const Graphics::Surface *frameSurf = decoder.decodeNextFrame();
if (!frameSurf) {
continue;
}
if (decoder.hasDirtyPalette()) {
_engine->setPalette(0, 256, decoder.getPalette());
}
Graphics::ManagedSurface& target = _engine->_frontVideoBuffer;
const Common::Rect frameBounds(0, 0, frameSurf->w, frameSurf->h);
target.blitFrom(*frameSurf, frameBounds, target.getBounds());
}
}
decoder.close();
return true;
}
} // namespace TwinE

112
engines/twine/movies.h Normal file
View File

@@ -0,0 +1,112 @@
/* 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 TWINE_FLAMOVIES_H
#define TWINE_FLAMOVIES_H
#include "common/memstream.h"
#include "common/scummsys.h"
#include "common/file.h"
#include "graphics/palette.h"
namespace TwinE {
/** Original FLA screen width */
#define FLASCREEN_WIDTH 320
/** Original FLA screen height */
#define FLASCREEN_HEIGHT 200
/** FLA movie header structure */
struct FLAHeaderStruct {
/** FLA version */
int8 version[5] {0};
/** Number of frames */
int32 numOfFrames = 0;
/** Frames per second */
int16 speed = 0;
/** Frame width */
int16 xsize = 0;
/** Frame height */
int16 ysize = 0;
};
/** FLA movie frame structure */
struct FLAFrameDataStruct {
int16 nbFrames = 0;
int32 offsetNextFrame = 0;
};
class TwinEEngine;
class Movies {
private:
TwinEEngine *_engine;
Common::File _file;
/** Auxiliar FLA fade out variable */
int32 _fadeOut = 0;
/** Auxiliar FLA fade out variable to count frames between the fade */
int32 _fadeOutFrames = 0;
bool _flagFirst = false;
Graphics::Palette _paletteOrg{0};
/** FLA movie file buffer */
uint8 _flaBuffer[FLASCREEN_WIDTH * FLASCREEN_HEIGHT] {0};
/** Number of samples in FLA movie */
int32 _samplesInFla = 0;
/** FLA movie header data */
FLAHeaderStruct _flaHeaderData;
/** FLA movie header data */
FLAFrameDataStruct _frameData;
void drawKeyFrame(Common::MemoryReadStream &stream, int32 width, int32 height);
void drawDeltaFrame(Common::MemoryReadStream &stream, int32 width);
/**
* Scale FLA movie 2 times
* According with the settins we can put the original aspect radio stretch
* to fullscreen or preserve it and use top and button black bars
*/
void scaleFla2x();
void drawNextFrameFla();
#ifdef USE_GIF
void prepareGIF(int index);
#endif
void playGIFMovie(const char *flaName);
bool playSmkMovie(const char *name, int index);
public:
Movies(TwinEEngine *engine);
/**
* Play FLA movies
* @param name FLA movie name
* @return @c true if finished. @c false if aborted.
*/
bool playMovie(const char *name);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,94 @@
/* 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 "twine/parser/anim.h"
#include "common/memstream.h"
namespace TwinE {
void AnimData::loadBoneFrame(KeyFrame &keyframe, Common::SeekableReadStream &stream) {
BoneFrame boneframe;
boneframe.type = (BoneType)stream.readSint16LE();
boneframe.x = stream.readSint16LE();
boneframe.y = stream.readSint16LE();
boneframe.z = stream.readSint16LE();
keyframe.boneframes.push_back(boneframe);
}
void AnimData::loadKeyFrames(Common::SeekableReadStream &stream) {
for (uint16 i = 0U; i < _numKeyframes; ++i) {
KeyFrame keyframe;
keyframe.length = stream.readUint16LE();
keyframe.x = stream.readSint16LE();
keyframe.y = stream.readSint16LE();
keyframe.z = stream.readSint16LE();
keyframe.animMasterRot = stream.readSint16LE();
keyframe.animStepAlpha = stream.readSint16LE();
keyframe.animStepBeta = stream.readSint16LE();
keyframe.animStepGamma = stream.readSint16LE();
stream.seek(-8, SEEK_CUR);
for (uint16 j = 0U; j < _numBoneframes; ++j) {
loadBoneFrame(keyframe, stream);
}
_keyframes.push_back(keyframe);
assert(keyframe.boneframes.size() == (uint)_numBoneframes);
}
}
void AnimData::reset() {
_keyframes.clear();
}
bool AnimData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
reset();
_numKeyframes = stream.readUint16LE();
_numBoneframes = stream.readUint16LE();
_loopFrame = stream.readUint16LE();
stream.readUint16LE();
loadKeyFrames(stream);
return !stream.err();
}
const Common::Array<KeyFrame>& AnimData::getKeyframes() const {
return _keyframes;
}
const KeyFrame* AnimData::getKeyframe(uint index) const {
if (index >= _numKeyframes) {
return nullptr;
}
return &_keyframes[index];
}
uint16 AnimData::getLoopFrame() const { // GetBouclageAnim
return _loopFrame;
}
uint16 AnimData::getNumBoneframes() const {
return _numBoneframes;
}
} // namespace TwinE

View File

@@ -0,0 +1,88 @@
/* 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 TWINE_PARSER_ANIM_H
#define TWINE_PARSER_ANIM_H
#include "common/array.h"
#include "common/stream.h"
#include "twine/parser/parser.h"
#include "twine/shared.h"
namespace TwinE {
enum BoneType : uint16 {
TYPE_ROTATE = 0,
TYPE_TRANSLATE = 1,
TYPE_ZOOM = 2,
};
struct BoneFrame { // T_GROUP_INFO
BoneType type = BoneType::TYPE_ROTATE;
int16 x = 0; // alpha
int16 y = 0; // beta
int16 z = 0; // gamma
};
using T_GROUP_INFO = BoneFrame; // (lba2)
struct KeyFrame {
uint16 length = 0;
int16 x = 0;
int16 y = 0;
int16 z = 0;
int16 animMasterRot = 0;
int16 animStepAlpha = 0;
int16 animStepBeta = 0;
int16 animStepGamma = 0;
Common::Array<BoneFrame> boneframes;
};
class AnimData : public Parser {
private:
Common::Array<KeyFrame> _keyframes;
void loadBoneFrame(KeyFrame &keyframe, Common::SeekableReadStream &stream);
void loadKeyFrames(Common::SeekableReadStream &stream);
uint16 _numKeyframes;
uint16 _numBoneframes;
uint16 _loopFrame;
protected:
void reset() override;
public:
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
const KeyFrame *getKeyframe(uint index) const;
const Common::Array<KeyFrame> &getKeyframes() const;
uint getNbFramesAnim() const;
uint16 getLoopFrame() const;
uint16 getNumBoneframes() const;
};
inline uint AnimData::getNbFramesAnim() const {
return getKeyframes().size();
}
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,42 @@
/* 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 "twine/parser/anim3ds.h"
#include "common/debug.h"
namespace TwinE {
bool Anim3DSData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
assert(!lba1);
const int n = (int)stream.size() / 8;
debug("preload %i anim3ds entries", n);
for (int i = 0; i < n; ++i) {
T_ANIM_3DS anim;
stream.read(anim.Name, sizeof(anim.Name));
anim.Deb = stream.readSint16LE();
anim.Fin = stream.readSint16LE();
_anims.push_back(anim);
}
return !stream.err();
}
} // namespace TwinE

View File

@@ -0,0 +1,48 @@
/* 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 TWINE_PARSER_ANIM3DS_H
#define TWINE_PARSER_ANIM3DS_H
#include "twine/parser/parser.h"
#include "twine/shared.h"
namespace TwinE {
struct T_ANIM_3DS {
char Name[4]; // Name of the animation
int16 Deb; // Start frame in the HQR
int16 Fin; // End frame in the HQR
};
class Anim3DSData : public Parser {
private:
Common::Array<T_ANIM_3DS> _anims; // ListAnim3DS
public:
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
const Common::Array<T_ANIM_3DS> &getAnims() const { return _anims; }
};
} // End of namespace TwinE
#endif

View 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/>.
*
*/
#include "twine/parser/blocklibrary.h"
#include "common/stream.h"
namespace TwinE {
void BlockLibraryData::reset() {
_layouts.clear();
}
bool BlockLibraryData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
reset();
const uint32 numLayouts = stream.readUint32LE() / 4;
_layouts.resize(numLayouts);
stream.seek(0);
for (uint32 i = 0; i < numLayouts; ++i) {
BlockData &blockData = _layouts[i];
const uint32 offset = stream.readUint32LE();
const uint32 nextOffset = stream.pos();
if (!stream.seek(offset)) {
return false;
}
if (!parseLayout(blockData, stream, lba1)) {
return false;
}
stream.seek(nextOffset);
}
return !stream.err();
}
bool BlockLibraryData::parseLayout(BlockData &blockData, Common::SeekableReadStream &stream, bool lba1) {
const uint8 x = stream.readByte();
const uint8 y = stream.readByte();
const uint8 z = stream.readByte();
const int32 numBricks = x * y * z;
blockData.entries.resize(numBricks);
for (int32 i = 0; i < numBricks; ++i) {
BlockDataEntry &blockEntry = blockData.entries[i];
blockEntry.brickShape = stream.readByte();
blockEntry.brickType = stream.readByte();
blockEntry.brickIdx = stream.readUint16LE();
blockEntry.sound = bits(blockEntry.brickType, 0, 4);
}
return !stream.err();
}
const BlockData *BlockLibraryData::getLayout(int index) const {
if (index < 0 || index >= (int)_layouts.size()) {
error("Block library index out of range: %i", index);
}
return &_layouts[index];
}
} // End of namespace TwinE

View File

@@ -0,0 +1,59 @@
/* 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 TWINE_PARSER_BLOCKLIBRARY_H
#define TWINE_PARSER_BLOCKLIBRARY_H
#include "common/array.h"
#include "common/stream.h"
#include "twine/parser/parser.h"
#include "twine/shared.h"
namespace TwinE {
struct BlockDataEntry {
uint8 brickShape;
uint8 brickType;
/**
* Index is not starting at 0 - but at 1. A 0 indicates an empty brick
*/
uint16 brickIdx;
uint8 sound;
};
struct BlockData {
Common::Array<BlockDataEntry> entries;
};
class BlockLibraryData : public Parser {
private:
Common::Array<BlockData> _layouts;
bool parseLayout(BlockData &blockData, Common::SeekableReadStream &stream, bool lba1);
protected:
void reset() override;
public:
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
const BlockData *getLayout(int index) const;
};
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,241 @@
/* 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 "twine/parser/body.h"
#include "twine/renderer/renderer.h"
#include "common/memstream.h"
#define INFO_TRI 1
#define INFO_ANIM 2
namespace TwinE {
void BodyData::reset() {
_vertices.clear();
_bones.clear();
_normals.clear();
_polygons.clear();
_spheres.clear();
_lines.clear();
}
void BodyData::loadVertices(Common::SeekableReadStream &stream) {
const uint16 numVertices = stream.readUint16LE();
if (stream.eos())
return;
_vertices.reserve(numVertices);
for (uint16 i = 0U; i < numVertices; ++i) {
const int16 x = stream.readSint16LE();
const int16 y = stream.readSint16LE();
const int16 z = stream.readSint16LE();
const uint16 bone = 0;
_vertices.push_back({x, y, z, bone});
}
}
void BodyData::loadBones(Common::SeekableReadStream &stream) {
const uint16 numBones = stream.readUint16LE();
if (stream.eos())
return;
_bones.reserve(numBones);
for (uint16 i = 0; i < numBones; ++i) {
const int16 firstPoint = stream.readSint16LE() / 6;
const int16 numPoints = stream.readSint16LE();
const int16 basePoint = stream.readSint16LE() / 6;
const int16 baseElementOffset = stream.readSint16LE();
BoneFrame boneframe;
boneframe.type = (BoneType)stream.readSint16LE();
boneframe.x = stream.readSint16LE();
boneframe.y = stream.readSint16LE();
boneframe.z = stream.readSint16LE();
/*int16 unk1 =*/ stream.readSint16LE();
const int16 numNormals = stream.readSint16LE();
/*int16 unk2 =*/ stream.readSint16LE();
/*int32 field_18 =*/ stream.readSint32LE();
/*int32 y =*/ stream.readSint32LE();
/*int32 field_20 =*/ stream.readSint32LE();
/*int32 field_24 =*/ stream.readSint32LE();
// PatchObjet in original sources
BodyBone bone;
bone.parent = baseElementOffset == -1 ? 0xffff : baseElementOffset / 38;
bone.vertex = basePoint;
bone.firstVertex = firstPoint;
bone.numVertices = numPoints;
bone.initalBoneState = boneframe;
bone.numNormals = numNormals;
// assign the bone index to the vertices
for (int j = 0; j < numPoints; ++j) {
_vertices[firstPoint + j].bone = i;
}
_bones.push_back(bone);
_boneStates[i] = bone.initalBoneState;
}
}
void BodyData::loadNormals(Common::SeekableReadStream &stream) {
const uint16 numNormals = stream.readUint16LE();
if (stream.eos())
return;
_normals.reserve(numNormals);
for (uint16 i = 0; i < numNormals; ++i) {
BodyNormal shape;
shape.x = stream.readSint16LE();
shape.y = stream.readSint16LE();
shape.z = stream.readSint16LE();
shape.prenormalizedRange = stream.readUint16LE();
_normals.push_back(shape);
}
}
void BodyData::loadPolygons(Common::SeekableReadStream &stream) {
const uint16 numPolygons = stream.readUint16LE();
if (stream.eos())
return;
_polygons.reserve(numPolygons);
for (uint16 i = 0; i < numPolygons; ++i) {
BodyPolygon poly;
poly.materialType = stream.readByte();
const uint8 numVertices = stream.readByte();
poly.intensity = stream.readSint16LE();
int16 normal = -1;
if (poly.materialType == MAT_FLAT || poly.materialType == MAT_GRANIT) {
// only one shade value is used
normal = stream.readSint16LE();
}
poly.indices.reserve(numVertices);
poly.normals.reserve(numVertices);
for (int k = 0; k < numVertices; ++k) {
if (poly.materialType >= MAT_GOURAUD) {
normal = stream.readSint16LE();
}
// numPoint is point index precomupted * 6
const uint16 vertexIndex = stream.readUint16LE() / 6;
poly.indices.push_back(vertexIndex);
poly.normals.push_back(normal);
}
_polygons.push_back(poly);
}
}
void BodyData::loadLines(Common::SeekableReadStream &stream) {
const uint16 numLines = stream.readUint16LE();
if (stream.eos())
return;
_lines.reserve(numLines);
for (uint16 i = 0; i < numLines; ++i) {
BodyLine line;
stream.skip(1);
line.color = stream.readByte();
stream.skip(2);
// indexPoint is point index precomupted * 6
line.vertex1 = stream.readUint16LE() / 6;
line.vertex2 = stream.readUint16LE() / 6;
_lines.push_back(line);
}
}
void BodyData::loadSpheres(Common::SeekableReadStream &stream) {
const uint16 numSpheres = stream.readUint16LE();
if (stream.eos())
return;
_spheres.reserve(numSpheres);
for (uint16 i = 0; i < numSpheres; ++i) {
BodySphere sphere;
sphere.fillType = stream.readByte();
sphere.color = stream.readUint16LE();
stream.readByte();
sphere.radius = stream.readUint16LE();
sphere.vertex = stream.readUint16LE() / 6;
_spheres.push_back(sphere);
}
}
bool BodyData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
reset();
if (lba1) {
const uint16 flags = stream.readUint16LE();
animated = (flags & INFO_ANIM) != 0;
bbox.mins.x = stream.readSint16LE();
bbox.maxs.x = stream.readSint16LE();
bbox.mins.y = stream.readSint16LE();
bbox.maxs.y = stream.readSint16LE();
bbox.mins.z = stream.readSint16LE();
bbox.maxs.z = stream.readSint16LE();
offsetToData = stream.readSint16LE();
// using this value as the offset crashes the demo of lba1 - see https://bugs.scummvm.org/ticket/14294
// stream.seek(offsetToData);
stream.seek(0x1A);
loadVertices(stream);
loadBones(stream);
loadNormals(stream);
loadPolygons(stream);
loadLines(stream);
loadSpheres(stream);
} else {
// T_BODY_HEADER (lba2)
const uint32 flags = stream.readUint32LE();
animated = (flags & INFO_ANIM) != 0;
stream.skip(4); // int16 size of header and int16 dummy
bbox.mins.x = stream.readSint32LE();
bbox.maxs.x = stream.readSint32LE();
bbox.mins.y = stream.readSint32LE();
bbox.maxs.y = stream.readSint32LE();
bbox.mins.z = stream.readSint32LE();
bbox.maxs.z = stream.readSint32LE();
stream.seek(0x20);
#if 0
const uint32 bonesSize = stream.readUint32LE();
const uint32 bonesOffset = stream.readUint32LE();
const uint32 verticesSize = stream.readUint32LE();
const uint32 verticesOffset = stream.readUint32LE();
const uint32 normalsSize = stream.readUint32LE();
const uint32 normalsOffset = stream.readUint32LE();
const uint32 unk1Size = stream.readUint32LE();
const uint32 unk1Offset = stream.readUint32LE();
const uint32 polygonsSize = stream.readUint32LE();
const uint32 polygonsOffset = stream.readUint32LE();
const uint32 linesSize = stream.readUint32LE();
const uint32 linesOffset = stream.readUint32LE();
const uint32 spheresSize = stream.readUint32LE();
const uint32 spheresOffset = stream.readUint32LE();
const uint32 uvGroupsSize = stream.readUint32LE();
const uint32 uvGroupsOffset = stream.readUint32LE();
#endif
}
return !stream.err();
}
} // namespace TwinE

126
engines/twine/parser/body.h Normal file
View File

@@ -0,0 +1,126 @@
/* 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 TWINE_PARSER_BODY_H
#define TWINE_PARSER_BODY_H
#include "common/array.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "twine/parser/anim.h"
#include "twine/parser/bodytypes.h"
#include "twine/parser/parser.h"
#include "twine/shared.h"
namespace TwinE {
/** Actors animation timer structure */
struct AnimTimerDataStruct {
const KeyFrame *ptr = nullptr;
int32 time = 0; // keyframe time
};
class BodyData : public Parser {
private:
void loadVertices(Common::SeekableReadStream &stream);
void loadBones(Common::SeekableReadStream &stream);
void loadNormals(Common::SeekableReadStream &stream);
void loadPolygons(Common::SeekableReadStream &stream);
void loadLines(Common::SeekableReadStream &stream);
void loadSpheres(Common::SeekableReadStream &stream);
Common::Array<BodyPolygon> _polygons;
Common::Array<BodyVertex> _vertices;
Common::Array<BodySphere> _spheres;
Common::Array<BodyNormal> _normals;
Common::Array<BodyLine> _lines;
Common::Array<BodyBone> _bones;
BoneFrame _boneStates[560];
protected:
void reset() override;
public:
bool animated = false;
AnimTimerDataStruct _animTimerData;
BoundingBox bbox;
int16 offsetToData = 0;
inline bool isAnimated() const {
return animated;
}
inline uint getNumBones() const {
return _bones.size();
}
inline uint getNumVertices() const {
return _vertices.size();
}
BoneFrame *getBoneState(int16 boneIdx) {
return &_boneStates[boneIdx];
}
const BoneFrame *getBoneState(int16 boneIdx) const {
return &_boneStates[boneIdx];
}
const Common::Array<BodyPolygon> &getPolygons() const {
return _polygons;
}
const Common::Array<BodyVertex> &getVertices() const {
return _vertices;
}
const Common::Array<BodySphere> &getSpheres() const {
return _spheres;
}
const Common::Array<BodyNormal> &getNormals() const {
return _normals;
}
const BodyNormal &getNormal(int16 normalIdx) const {
return _normals[normalIdx];
}
const Common::Array<BodyLine> &getLines() const {
return _lines;
}
const Common::Array<BodyBone> &getBones() const {
return _bones;
}
const BodyBone &getBone(int16 boneIdx) const {
return _bones[boneIdx];
}
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
};
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,83 @@
/* 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 TWINE_PARSER_BODYTYPES_H
#define TWINE_PARSER_BODYTYPES_H
#include "common/array.h"
#include "twine/shared.h"
#include "twine/parser/anim.h"
namespace TwinE {
struct BodyVertex {
int16 x;
int16 y;
int16 z;
uint16 bone;
};
struct BodyLine {
// fill byte here
uint8 color;
// 2 fill bytes here
uint16 vertex1;
uint16 vertex2;
};
struct BodySphere {
uint8 fillType;
uint16 color; // start and end color index
// fill byte here
uint16 radius;
uint16 vertex;
};
struct BodyBone {
uint16 parent;
uint16 vertex;
int16 firstVertex;
int16 numVertices;
int32 numNormals;
BoneFrame initalBoneState;
inline bool isRoot() const {
return parent == 0xffff;
}
};
struct BodyNormal {
int16 x;
int16 y;
int16 z;
uint16 prenormalizedRange;
};
struct BodyPolygon {
Common::Array<uint16> indices;
Common::Array<uint16> normals;
int8 materialType = 0;
int16 intensity = 0; // color1 / color2
};
}
#endif

View File

@@ -0,0 +1,341 @@
/* 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 "twine/parser/entity.h"
#include "common/stream.h"
#include "twine/resources/resources.h"
#include "twine/shared.h"
namespace TwinE {
bool EntityData::loadBody(Common::SeekableReadStream &stream, bool lba1) {
EntityBody body;
body.index = stream.readByte();
const int64 pos = stream.pos();
uint8 size = stream.readByte();
body.hqrBodyIndex = (int16)stream.readUint16LE();
if (!body.body.loadFromHQR(TwineResource(Resources::HQR_BODY_FILE, body.hqrBodyIndex), lba1)) {
error("Failed to load body with index: %i", body.hqrBodyIndex);
}
const uint8 numActions = stream.readByte();
for (uint8 i = 0U; i < numActions; ++i) {
if ((ActionType)stream.readByte() == ActionType::ACTION_ZV) {
body.actorBoundingBox.hasBoundingBox = true;
body.actorBoundingBox.bbox.mins.x = stream.readSint16LE();
body.actorBoundingBox.bbox.mins.y = stream.readSint16LE();
body.actorBoundingBox.bbox.mins.z = stream.readSint16LE();
body.actorBoundingBox.bbox.maxs.x = stream.readSint16LE();
body.actorBoundingBox.bbox.maxs.y = stream.readSint16LE();
body.actorBoundingBox.bbox.maxs.z = stream.readSint16LE();
}
}
_bodies.push_back(body);
stream.seek(pos + size);
return !stream.err();
}
bool EntityData::loadAnim(Common::SeekableReadStream &stream, bool lba1) {
EntityAnim anim;
if (lba1) {
anim.animation = (AnimationTypes)stream.readByte();
} else {
anim.animation = (AnimationTypes)stream.readUint16LE();
}
const int64 pos = stream.pos();
uint8 size = stream.readByte();
anim.animIndex = stream.readSint16LE();
const uint8 numActions = stream.readByte();
for (uint8 i = 0U; i < numActions; ++i) {
EntityAnim::Action action;
action.type = (ActionType)stream.readByte();
switch (action.type) {
case ActionType::ACTION_HITTING:
action.animFrame = stream.readByte();
action.strength = stream.readByte();
break;
case ActionType::ACTION_SAMPLE:
action.animFrame = stream.readByte();
action.sampleIndex = stream.readSint16LE();
break;
case ActionType::ACTION_SAMPLE_FREQ:
action.animFrame = stream.readByte();
action.sampleIndex = stream.readSint16LE();
action.frequency = stream.readSint16LE();
break;
case ActionType::ACTION_THROW_MAGIC_BALL:
action.animFrame = stream.readByte();
action.yHeight = stream.readSint16LE();
action.xAngle = ToAngle(stream.readSint16LE());
action.xRotPoint = stream.readSint16LE();
action.extraAngle = stream.readByte();
break;
case ActionType::ACTION_SAMPLE_REPEAT:
action.animFrame = stream.readByte();
action.sampleIndex = stream.readSint16LE();
action.repeat = stream.readSint16LE();
if (!lba1) {
action.decal = stream.readSint16LE();
action.sampleVolume = stream.readByte();
action.frequency = stream.readSint16LE();
}
break;
case ActionType::ACTION_THROW_SEARCH:
action.animFrame = stream.readByte();
action.yHeight = stream.readSint16LE();
action.spriteIndex = stream.readByte();
action.targetActor = stream.readByte();
action.finalAngle = stream.readSint16LE();
action.strength = stream.readByte();
break;
case ActionType::ACTION_THROW_EXTRA_BONUS:
case ActionType::ACTION_THROW_ALPHA:
action.animFrame = stream.readByte();
action.yHeight = stream.readSint16LE();
action.spriteIndex = stream.readByte();
action.xAngle = ToAngle(stream.readSint16LE());
action.yAngle = ToAngle(stream.readSint16LE());
action.xRotPoint = stream.readSint16LE();
// TODO: does lba2 need a scaling here for xRotPoint?
action.extraAngle = ToAngle(stream.readByte());
action.strength = stream.readByte();
break;
case ActionType::ACTION_LEFT_STEP:
case ActionType::ACTION_RIGHT_STEP:
case ActionType::ACTION_HERO_HITTING:
action.animFrame = stream.readByte();
break;
case ActionType::ACTION_SAMPLE_STOP:
action.animFrame = stream.readByte();
if (lba1) {
action.sampleIndex = stream.readByte();
stream.skip(1);
} else {
action.sampleIndex = stream.readUint16LE();
}
break;
case ActionType::ACTION_THROW_3D:
case ActionType::ACTION_THROW_3D_ALPHA:
action.animFrame = stream.readByte();
action.distanceX = stream.readSint16LE();
action.distanceY = stream.readSint16LE();
action.distanceZ = stream.readSint16LE();
action.spriteIndex = stream.readByte();
action.xAngle = ToAngle(stream.readSint16LE());
action.yAngle = ToAngle(stream.readSint16LE());
action.xRotPoint = stream.readSint16LE();
action.extraAngle = ToAngle(stream.readByte());
action.strength = stream.readByte();
break;
case ActionType::ACTION_THROW_3D_SEARCH:
action.animFrame = stream.readByte();
action.distanceX = stream.readSint16LE();
action.distanceY = stream.readSint16LE();
action.distanceZ = stream.readSint16LE();
action.spriteIndex = stream.readByte();
action.targetActor = stream.readByte();
action.finalAngle = ToAngle(stream.readSint16LE());
action.strength = stream.readByte();
break;
case ActionType::ACTION_THROW_3D_MAGIC:
action.animFrame = stream.readByte();
action.distanceX = stream.readSint16LE();
action.distanceY = stream.readSint16LE();
action.distanceZ = stream.readSint16LE();
action.xAngle = stream.readSint16LE();
action.yAngle = stream.readSint16LE();
action.finalAngle = stream.readByte();
break;
case ActionType::ACTION_ZV:
default:
break;
}
if (!lba1) {
switch (action.type) {
case ActionType::ACTION_ZV:
action.animFrame = stream.readByte();
action.bbox.mins.x = stream.readSint16LE();
action.bbox.mins.y = stream.readSint16LE();
action.bbox.mins.z = stream.readSint16LE();
action.bbox.maxs.x = stream.readSint16LE();
action.bbox.maxs.y = stream.readSint16LE();
action.bbox.maxs.z = stream.readSint16LE();
break;
case ActionType::ACTION_SUPER_HIT:
action.animFrame = stream.readByte();
action.strength = stream.readByte();
action.superHitX = stream.readSint16LE();
action.superHitY = stream.readSint16LE();
action.superHitZ = stream.readSint16LE();
action.sizeSuperHit = stream.readSint16LE();
break;
case ActionType::ACTION_THROW_OBJ_3D:
action.animFrame = stream.readByte();
action.distanceX = stream.readSint16LE();
action.distanceY = stream.readSint16LE();
action.distanceZ = stream.readSint16LE();
action.spriteIndex = stream.readByte();
action.xAngle = ToAngle(stream.readSint16LE());
action.yAngle = ToAngle(stream.readSint16LE());
action.xRotPoint = stream.readSint16LE();
action.extraAngle = ToAngle(stream.readByte());
action.strength = stream.readByte();
break;
case ActionType::ACTION_NEW_SAMPLE:
action.animFrame = stream.readByte();
action.sampleIndex = stream.readSint16LE();
action.decal = stream.readSint16LE();
action.sampleVolume = stream.readByte();
action.frequency = stream.readSint16LE();
break;
case ActionType::ACTION_THROW_DART:
action.animFrame = stream.readByte();
action.distanceY = stream.readSint16LE();
action.xAngle = ToAngle(stream.readSint16LE());
action.speed = stream.readSint16LE();
action.weight = stream.readSByte();
break;
case ActionType::ACTION_SHIELD:
action.animFrame = stream.readByte();
action.lastAnimFrame = stream.readByte();
break;
case ActionType::ACTION_FLOW_3D:
case ActionType::ACTION_THROW_3D_CONQUE:
action.animFrame = stream.readByte();
action.xAngle = ToAngle(stream.readSint16LE());
action.yHeight = stream.readSint16LE();
action.yAngle = ToAngle(stream.readSint16LE());
action.spriteIndex = stream.readByte();
break;
case ActionType::ACTION_IMPACT:
case ActionType::ACTION_RENVOYABLE:
action.animFrame = stream.readByte();
action.strength = stream.readSint16LE();
break;
case ActionType::ACTION_SCALE:
action.animFrame = stream.readByte();
action.scale = stream.readSint32LE();
break;
case ActionType::ACTION_IMPACT_3D:
action.animFrame = stream.readByte();
action.xAngle = ToAngle(stream.readSint16LE());
action.yHeight = stream.readSint16LE();
action.yAngle = ToAngle(stream.readSint16LE());
action.spriteIndex = stream.readSint16LE();
break;
case ActionType::ACTION_THROW_MAGIC_EXTRA:
action.animFrame = stream.readByte();
action.pointIndex = stream.readSint16LE();
action.spriteIndex = stream.readByte();
action.xAngle = ToAngle(stream.readSint16LE());
action.speed = stream.readSint16LE();
action.weight = stream.readSByte();
break;
case ActionType::ACTION_ZV_ANIMIT:
case ActionType::ACTION_RENVOIE:
case ActionType::ACTION_TRANSPARENT:
case ActionType::ACTION_SAMPLE_MAGIC:
case ActionType::ACTION_LEFT_JUMP:
case ActionType::ACTION_RIGHT_JUMP:
case ActionType::ACTION_THROW_FOUDRE:
action.animFrame = stream.readByte();
/* empty */
break;
case ActionType::ACTION_PATH:
case ActionType::ACTION_FLOW:
default:
break;
}
}
if (action.type > ActionType::ACTION_THROW_FOUDRE) {
error("Unknown action type %d", (int)action.type);
}
anim._actions.push_back(action);
}
_animations.push_back(anim);
stream.seek(pos + size);
return !stream.err();
}
void EntityData::reset() {
_animations.clear();
_bodies.clear();
}
bool EntityData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
reset();
do {
const uint8 opcode = stream.readByte();
if (opcode == 1) {
if (!loadBody(stream, lba1)) {
return false;
}
} else if (opcode == 3) {
if (!loadAnim(stream, lba1)) {
return false;
}
} else if (opcode == 0xFF) {
break;
}
} while (!stream.eos() && !stream.err());
return true;
}
const Common::Array<EntityAnim::Action> *EntityData::getActions(AnimationTypes animation) const {
for (const EntityAnim &anim : _animations) {
if (anim.animation == animation) {
if (anim._actions.empty()) {
return nullptr;
}
return &anim._actions;
}
}
return nullptr;
}
BodyData &EntityData::getBody(int index) {
for (EntityBody &body : _bodies) {
if (body.index == index) {
return body.body;
}
}
error("Could not find body for index: %i", index);
}
const EntityBody *EntityData::getEntityBody(const int index) const {
for (const EntityBody &body : _bodies) {
if (body.index == index) {
return &body;
}
}
return nullptr;
}
int32 EntityData::getAnimIndex(AnimationTypes animation) const {
for (const EntityAnim &anim : _animations) {
if (anim.animation == animation) {
return anim.animIndex;
}
}
return -1;
}
} // End of namespace TwinE

View File

@@ -0,0 +1,119 @@
/* 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 TWINE_PARSER_ENTITY_H
#define TWINE_PARSER_ENTITY_H
#include "common/array.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "twine/parser/body.h"
#include "twine/parser/parser.h"
#include "twine/shared.h"
namespace TwinE {
struct EntityBody {
int index; /**< index in file3d.hqr */
ActorBoundingBox actorBoundingBox;
int hqrBodyIndex; /**< index in body.hqr */
BodyData body;
};
struct EntityAnim {
AnimationTypes animation;
int animIndex;
struct Action {
ActionType type = ActionType::ACTION_NOP;
uint8 animFrame = 0;
uint8 lastAnimFrame = 0;
int8 weight = 0;
byte sampleVolume = 0;
int16 pointIndex = 0;
int16 spriteIndex = 0;
uint8 targetActor = 0;
int16 sampleIndex = 0;
int16 frequency = 0;
int16 xAngle = 0;
int16 yAngle = 0;
int16 xRotPoint = 0;
int16 extraAngle = 0;
int16 finalAngle = 0;
int16 strength = 0;
int16 distanceX = 0;
int16 distanceY = 0;
int16 distanceZ = 0;
int16 yHeight = 0;
int16 repeat = 0;
int16 speed = 0;
int16 superHitX = 0;
int16 superHitY = 0;
int16 superHitZ = 0;
int16 sizeSuperHit = 0;
int16 decal = 0;
int32 scale = 0;
BoundingBox bbox;
};
Common::Array<Action> _actions;
};
/**
* @brief Associate 3d models from body hqr with animations from anim.hqr for the game characters
*/
class EntityData : public Parser {
private:
Common::Array<EntityBody> _bodies;
Common::Array<EntityAnim> _animations;
bool loadBody(Common::SeekableReadStream &stream, bool lba1);
bool loadAnim(Common::SeekableReadStream &stream, bool lba1);
protected:
void reset() override;
public:
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
const Common::Array<EntityAnim::Action> *getActions(AnimationTypes animation) const;
const EntityBody *getEntityBody(const int index) const;
BodyData &getBody(int index);
int32 getAnimIndex(AnimationTypes animation) const;
const Common::Array<EntityBody> &getBodies() const {
return _bodies;
}
const Common::Array<EntityAnim> &getAnimations() const {
return _animations;
}
Common::Array<EntityBody> &getBodies() {
return _bodies;
}
Common::Array<EntityAnim> &getAnimations() {
return _animations;
}
};
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,54 @@
/* 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 "twine/parser/holomap.h"
#include "common/debug.h"
#include "common/stream.h"
namespace TwinE {
void TrajectoryData::reset() {
_trajectories.clear();
}
bool TrajectoryData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
reset();
_trajectories.reserve(100); // this is the lba1 amount of trajectories
while (stream.pos() < stream.size()) {
Trajectory data;
data.locationIdx = stream.readSint16LE();
data.trajLocationIdx = stream.readSint16LE();
data.vehicleIdx = stream.readSint16LE();
data.angle.x = stream.readSint16LE();
data.angle.y = stream.readSint16LE();
data.angle.z = stream.readSint16LE();
data.numAnimFrames = stream.readSint16LE();
assert(data.numAnimFrames < ARRAYSIZE(data.positions));
for (int32 i = 0; i < data.numAnimFrames; ++i) {
data.positions[i].x = stream.readSint16LE();
data.positions[i].y = stream.readSint16LE();
}
_trajectories.push_back(data);
}
return !stream.err();
}
} // End of namespace TwinE

View File

@@ -0,0 +1,94 @@
/* 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 TWINE_PARSER_HOLOMAP_H
#define TWINE_PARSER_HOLOMAP_H
#include "common/memstream.h"
#include "twine/parser/parser.h"
namespace TwinE {
enum HolomapVehicle {
FerryBoat = 31,
Motorbike = 33,
Car = 35,
FishingBoat = 37,
Catamaran = 39,
Hovercraft = 41,
Dino = 43,
ArmyBoat = 45,
HamalayiTransporter = 47
};
struct TrajectoryPos {
int16 x = 0;
int16 y = 0;
};
struct Trajectory {
int16 locationIdx = -1;
int16 trajLocationIdx = -1;
int16 vehicleIdx = -1;
IVec3 angle;
int16 numAnimFrames = 0;
TrajectoryPos positions[512];
bool isValid() const {
return locationIdx != -1;
}
/**
* The HQR index of the vehicle model for the holomap
* @note Multiplied by 2 because the model index is always followed by the corresponding animation index for that model
*/
int32 getModel() const {
return 2 * vehicleIdx + HolomapVehicle::FerryBoat;
}
int32 getAnimation() const {
return getModel() + 1;
}
};
class TrajectoryData : public Parser {
private:
Common::Array<Trajectory> _trajectories;
protected:
void reset() override;
public:
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
const Trajectory *getTrajectory(uint index) const {
if (index >= _trajectories.size()) {
return nullptr;
}
return &_trajectories[index];
}
const Common::Array<Trajectory> &getTrajectories() const {
return _trajectories;
}
};
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,52 @@
/* 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 "twine/parser/parser.h"
#include "common/stream.h"
#include "twine/resources/hqr.h"
#include "twine/shared.h"
namespace TwinE {
bool Parser::loadFromBuffer(const uint8 *buf, uint32 size, bool lba1) {
if (size == 0) {
return false;
}
Common::MemoryReadStream stream(buf, size);
return loadFromStream(stream, lba1);
}
bool Parser::loadFromHQR(const char *name, int index, bool lba1) {
Common::SeekableReadStream *stream = HQR::makeReadStream(name, index);
if (stream == nullptr) {
warning("Failed to load %s with index %i", name, index);
return false;
}
if (!loadFromStream(*stream, lba1)) {
delete stream;
return false;
}
_hqrIndex = index;
delete stream;
return true;
}
} // End of namespace TwinE

View File

@@ -0,0 +1,56 @@
/* 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 TWINE_PARSER_PARSER_H
#define TWINE_PARSER_PARSER_H
#include "common/array.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "twine/shared.h"
namespace TwinE {
class Parser {
protected:
int _hqrIndex = -1;
virtual void reset() {}
public:
virtual ~Parser() {
reset();
}
virtual bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) = 0;
bool loadFromBuffer(const uint8 *buf, uint32 size, bool lba1);
bool loadFromHQR(const char *name, int index, bool lba1);
int hqrIndex() const {
return _hqrIndex;
}
inline bool loadFromHQR(const TwineResource &resource, bool lba1) {
return loadFromHQR(resource.hqr, resource.index, lba1);
}
};
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,118 @@
/* 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 "twine/parser/sprite.h"
#include "common/stream.h"
#include "twine/shared.h"
namespace TwinE {
bool SpriteBoundingBoxData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
const int32 size = stream.size();
const int32 amount = size / 16;
for (int32 i = 0; i < amount; ++i) {
SpriteDim spriteDim;
spriteDim.x = stream.readSint16LE();
spriteDim.y = stream.readSint16LE();
BoundingBox boundingBox;
boundingBox.mins.x = stream.readSint16LE();
boundingBox.maxs.x = stream.readSint16LE();
boundingBox.mins.y = stream.readSint16LE();
boundingBox.maxs.y = stream.readSint16LE();
boundingBox.mins.z = stream.readSint16LE();
boundingBox.maxs.z = stream.readSint16LE();
_boundingBoxes.push_back(boundingBox);
_dimensions.push_back(spriteDim);
}
return !stream.err();
}
void SpriteData::reset() {
for (int i = 0; i < _sprites; ++i) {
_surfaces[i].free();
}
_sprites = 0;
}
bool SpriteData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
reset();
if (_bricks) {
// brick sprites don't have the offsets
return loadSprite(stream, 0);
}
const uint32 offset1 = stream.readUint32LE();
const uint32 offset2 = stream.readUint32LE();
const uint32 offsetData = stream.pos();
if (!loadSprite(stream, offset1)) {
return false;
}
// for most sprites the second offset is just the end of the stream - but
// some sprites (like shadow in lba1) have a second sprite following the
// first one.
if (offset2 + offsetData >= stream.size()) {
return true;
}
return loadSprite(stream, offset2);
}
bool SpriteData::loadSprite(Common::SeekableReadStream &stream, uint32 offset) {
stream.seek(offset);
int width = stream.readByte();
int height = stream.readByte();
_offsetX[_sprites] = stream.readByte();
_offsetY[_sprites] = stream.readByte();
const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
_surfaces[_sprites].create(width, height, format);
const uint8 *last = (const uint8 *)_surfaces[_sprites].getBasePtr(width, height - 1);
for (int y = 0; y < height; ++y) {
const uint8 numRuns = stream.readByte();
int x = 0;
for (uint8 run = 0; run < numRuns; ++run) {
const uint8 runSpec = stream.readByte();
const uint8 runLength = bits(runSpec, 0, 6) + 1;
const uint8 type = bits(runSpec, 6, 2);
if (type == 1) {
uint8 *start = (uint8 *)_surfaces[_sprites].getBasePtr(x, y);
for (uint8 i = 0; i < runLength; ++i) {
if (start > last) {
return false;
}
*start++ = stream.readByte();
}
} else if (type != 0) {
uint8 *start = (uint8 *)_surfaces[_sprites].getBasePtr(x, y);
uint8 *end = (uint8 *)_surfaces[_sprites].getBasePtr(x + runLength, y);
if (end > last) {
return false;
}
Common::fill(start, end, stream.readByte());
}
x += runLength;
}
}
if (stream.err()) {
return false;
}
++_sprites;
return true;
}
} // namespace TwinE

View File

@@ -0,0 +1,116 @@
/* 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 TWINE_PARSER_SPRITE_H
#define TWINE_PARSER_SPRITE_H
#include "common/array.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "graphics/managed_surface.h"
#include "twine/parser/parser.h"
#include "twine/shared.h"
namespace TwinE {
struct SpriteDim {
int16 x = 0;
int16 y = 0;
int16 w = 0;
int16 h = 0;
};
// PtrZvExtra
class SpriteBoundingBoxData : public Parser {
private:
Common::Array<BoundingBox> _boundingBoxes;
Common::Array<SpriteDim> _dimensions;
public:
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
const BoundingBox *bbox(int index) const; // PtrZvAnim3DS, PtrZvExtra, PtrZvExtraRaw
const SpriteDim *dim(int index) const;
};
inline const BoundingBox *SpriteBoundingBoxData::bbox(int index) const {
if (index < 0) {
return nullptr;
}
return &_boundingBoxes[index];
}
inline const SpriteDim *SpriteBoundingBoxData::dim(int index) const {
if (index < 0) {
return nullptr;
}
return &_dimensions[index];
}
class SpriteData : public Parser {
protected:
Graphics::ManagedSurface _surfaces[2];
int _offsetX[2] {0};
int _offsetY[2] {0};
int _sprites = 0;
bool _bricks = false;
bool loadSprite(Common::SeekableReadStream &stream, uint32 offset);
void reset() override;
public:
bool loadFromStream(Common::SeekableReadStream &stream, bool lba1) override;
inline const Graphics::ManagedSurface &surface(int index = 0) const {
if (index < 0 || index >= _sprites) {
error("Sprite surface index out of range: %i (max: %i)", index, _sprites);
}
return _surfaces[index];
}
inline int sprites() const {
return _sprites;
}
inline int offsetX(int index = 0) const {
if (index < 0 || index >= _sprites) {
error("Sprite offset index out of range: %i (max: %i)", index, _sprites);
}
return _offsetX[index];
}
inline int offsetY(int index = 0) const {
if (index < 0 || index >= _sprites) {
error("Sprite offset index out of range: %i (max: %i)", index, _sprites);
}
return _offsetY[index];
}
};
class BrickData : public SpriteData {
public:
BrickData() {
_bricks = true;
}
};
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,107 @@
/* 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 "twine/parser/text.h"
#include "common/debug.h"
#include "common/str-enc.h"
#include "common/util.h"
#include "common/translation.h"
#include "twine/resources/hqr.h"
#include "twine/shared.h"
namespace TwinE {
void TextData::initCustomTexts(TextBankId textBankId) {
if (textBankId == TextBankId::Options_and_menus) {
// TODO: add resource file for these custom strings to support other languages
add(textBankId, TextEntry{Common::U32String("High resolution on").encode(Common::CodePage::kDos850), -1, TextId::kCustomHighResOptionOn});
add(textBankId, TextEntry{Common::U32String("High resolution off").encode(Common::CodePage::kDos850), -1, TextId::kCustomHighResOptionOff});
add(textBankId, TextEntry{Common::U32String("Wall collision on").encode(Common::CodePage::kDos850), -1, TextId::kCustomWallCollisionOn});
add(textBankId, TextEntry{Common::U32String("Wall collision off").encode(Common::CodePage::kDos850), -1, TextId::kCustomWallCollisionOff});
add(textBankId, TextEntry{Common::U32String("Language selection").encode(Common::CodePage::kDos850), -1, TextId::kCustomLanguageOption});
add(textBankId, TextEntry{Common::U32String("Voices: None").encode(Common::CodePage::kDos850), -1, TextId::kCustomVoicesNone});
add(textBankId, TextEntry{Common::U32String("Voices: English").encode(Common::CodePage::kDos850), -1, TextId::kCustomVoicesEnglish});
add(textBankId, TextEntry{Common::U32String("Voices: French").encode(Common::CodePage::kDos850), -1, TextId::kCustomVoicesFrench});
add(textBankId, TextEntry{Common::U32String("Voices: German").encode(Common::CodePage::kDos850), -1, TextId::kCustomVoicesGerman});
}
}
bool TextData::loadFromHQR(const char *name, TextBankId textBankId, int language, bool lba1, int entryCount) {
const int langIdx = (int)textBankId * 2 + (entryCount * language);
Common::SeekableReadStream *indexStream = HQR::makeReadStream(name, langIdx + 0);
Common::SeekableReadStream *offsetStream = HQR::makeReadStream(name, langIdx + 1);
if (indexStream == nullptr || offsetStream == nullptr) {
warning("Failed to load %s with index %i", name, langIdx);
delete indexStream;
delete offsetStream;
return false;
}
_texts[(int)textBankId].clear();
initCustomTexts(textBankId);
const int numIdxEntries = (int)indexStream->size() / 2;
_texts[(int)textBankId].reserve(numIdxEntries + _texts[(int)textBankId].size());
for (int entry = 0; entry < numIdxEntries; ++entry) {
const TextId textIdx = (TextId)indexStream->readUint16LE();
uint16 start = offsetStream->readUint16LE();
const int32 offsetPos = offsetStream->pos();
const uint16 end = offsetStream->readUint16LE();
if (!lba1) {
++start;
}
offsetStream->seek(start);
Common::String result;
for (int16 i = start; i < end - 1; ++i) {
const char c = (char)offsetStream->readByte();
if (c == '\0') {
break;
}
result += c;
}
add(textBankId, TextEntry{result, entry, textIdx});
debugC(2, TwinE::kDebugResources, "index: %i (bank %i), text: %s", (int)textIdx, (int)textBankId, result.c_str());
offsetStream->seek(offsetPos);
if (end >= offsetStream->size()) {
break;
}
}
delete indexStream;
delete offsetStream;
return true;
}
const TextEntry *TextData::getText(TextBankId textBankId, TextId textIndex) const {
const Common::Array<TextEntry> &entries = _texts[(int)textBankId];
const int32 size = entries.size();
for (int32 i = 0; i < size; ++i) {
if (entries[i].textIndex == textIndex) {
return &entries[i];
}
}
debugC(1, TwinE::kDebugResources, "Failed to find text entry for bank id %i with text index %i", (int)textBankId, (int)textIndex);
return nullptr;
}
} // End of namespace TwinE

View 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 TWINE_PARSER_TEXT_H
#define TWINE_PARSER_TEXT_H
#include "common/array.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "twine/parser/parser.h"
#include "twine/shared.h"
namespace TwinE {
class TextEntry {
public:
Common::String string; /**< The real string behind the text id */
int index; /**< The index in the text index hqr file. This is also the index in the corresponding vox hqr file */
TextId textIndex; /**< The text identifier */
};
class TextData {
private:
// 30 is the max for lba2, lba1 uses 28
Common::Array<TextEntry> _texts[30];
void add(TextBankId textBankId, const TextEntry &entry) {
_texts[(int)textBankId].push_back(entry);
}
// custom texts that are not included in the original game
void initCustomTexts(TextBankId textBankId);
public:
bool loadFromHQR(const char *name, TextBankId textBankId, int language, bool lba1, int entryCount);
const TextEntry *getText(TextBankId textBankId, TextId textIndex) const;
};
} // End of namespace TwinE
#endif

View File

@@ -0,0 +1,979 @@
/* 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 "twine/renderer/redraw.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "graphics/surface.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_state.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/menu/menu.h"
#include "twine/parser/body.h"
#include "twine/parser/sprite.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/extra.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
namespace TwinE {
Redraw::Redraw(TwinEEngine *engine) : _engine(engine), _bubbleSpriteIndex(SPRITEHQR_DIAG_BUBBLE_LEFT) {}
void Redraw::addRedrawCurrentArea(const Common::Rect &redrawArea) {
const int32 area = (redrawArea.right - redrawArea.left) * (redrawArea.bottom - redrawArea.top);
for (int32 i = 0; i < _nbOptPhysBox; ++i) {
Common::Rect &rect = _currentRedrawList[i];
const int32 leftValue = MIN<int32>(redrawArea.left, rect.left);
const int32 rightValue = MAX<int32>(redrawArea.right, rect.right);
const int32 topValue = MIN<int32>(redrawArea.top, rect.top);
const int32 bottomValue = MAX<int32>(redrawArea.bottom, rect.bottom);
const int32 areaValue = (rightValue - leftValue) * (bottomValue - topValue);
const int32 areaValueDiff = ((rect.right - rect.left) * (rect.bottom - rect.top) + area);
if (areaValue < areaValueDiff) {
rect.left = leftValue;
rect.top = topValue;
rect.right = rightValue;
rect.bottom = MIN<int32>((_engine->height() - 1), bottomValue);
assert(rect.left <= rect.right);
assert(rect.top <= rect.bottom);
return;
}
}
Common::Rect &rect = _currentRedrawList[_nbOptPhysBox];
rect.left = redrawArea.left;
rect.top = redrawArea.top;
rect.right = redrawArea.right;
rect.bottom = MIN<int32>((_engine->height() - 1), redrawArea.bottom);
assert(rect.left <= rect.right);
assert(rect.top <= rect.bottom);
_nbOptPhysBox++;
}
void Redraw::addPhysBox(const Common::Rect &rect) {
if (!rect.isValidRect()) {
return;
}
addRedrawArea(rect.left, rect.top, rect.right, rect.bottom);
}
void Redraw::addRedrawArea(int32 left, int32 top, int32 right, int32 bottom) {
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
if (right >= _engine->width()) {
right = (_engine->width() - 1);
}
if (bottom >= _engine->height()) {
bottom = (_engine->height() - 1);
}
if (left > right || top > bottom) {
return;
}
Common::Rect &rect = _nextRedrawList[_nbPhysBox];
rect.left = left;
rect.top = top;
rect.right = right;
rect.bottom = bottom;
_nbPhysBox++;
addRedrawCurrentArea(rect);
}
void Redraw::moveNextAreas() {
_nbOptPhysBox = 0;
for (int32 i = 0; i < _nbPhysBox; i++) {
addRedrawCurrentArea(_nextRedrawList[i]);
}
}
void Redraw::flipBoxes() {
for (int32 i = 0; i < _nbOptPhysBox; i++) { // redraw areas on screen
_engine->copyBlockPhys(_currentRedrawList[i]);
}
moveNextAreas();
}
void Redraw::clsBoxes() {
for (int32 i = 0; i < _nbOptPhysBox; i++) {
_engine->blitWorkToFront(_currentRedrawList[i]);
}
}
void Redraw::sortDrawingList(DrawListStruct *list, int32 listSize) const { // SmallSort
DrawListStruct* pNext;
DrawListStruct* pSmallest;
int32 n;
for (listSize--; listSize > 0; listSize--) {
pSmallest = list;
pNext = list + 1;
for (n = listSize; n > 0; n--) {
if (pNext->z < pSmallest->z) {
pSmallest = pNext;
}
pNext++;
}
if (pSmallest != list) {
DrawListStruct tmp = *pSmallest;
*pSmallest = *list;
*list = tmp;
}
list++;
}
}
void Redraw::posObjIncrust(OverlayListStruct *ptrdisp, int32 num) {
// in case we have several 3D objects rotating at the same time!
int32 x = 10;
OverlayType type = ptrdisp->type;
if (type == OverlayType::koInventory || type == OverlayType::koInventoryItem) {
for (int32 n = 0; n < ARRAYSIZE(overlayList); n++) {
OverlayListStruct *overlay = &overlayList[n];
if (n != num && overlay->num != -1) {
if (overlay->type == OverlayType::koInventory || overlay->type == OverlayType::koInventoryItem) {
x += 70;
}
}
}
ptrdisp->y = 10;
ptrdisp->x = (int16)x;
}
}
int32 Redraw::addOverlay(OverlayType type, int16 info0, int16 x, int16 y, int16 info1, OverlayPosType posType, int16 lifeTime) { // InitIncrustDisp
for (int32 i = 0; i < ARRAYSIZE(overlayList); i++) {
OverlayListStruct *overlay = &overlayList[i];
if (_engine->isLBA1()) {
if (overlay->num == -1) {
overlay->type = type;
overlay->num = info0;
overlay->x = x;
overlay->y = y;
overlay->info = info1;
overlay->move = posType;
overlay->timerEnd = _engine->timerRef + _engine->toSeconds(lifeTime);
return i;
}
} else {
if (overlay->num == -1 || (overlay->num == info0 && overlay->type == type)) {
if (overlay->num == -1 || overlay->type != type) {
overlay->x = x;
overlay->y = y;
}
if ((OverlayType)((uint8)type) == OverlayType::koNumberRange) {
// ATTENTION: Big Trickery: counters are always displayed
// at y=20, this allows using the Y to store the
// current value of the counter (see FlagAnimWhoSpeak)
overlay->y = info0;
}
overlay->type = type;
overlay->num = info0;
overlay->info = info1;
overlay->move = posType;
overlay->timerEnd = _engine->timerRef + _engine->toSeconds(lifeTime);
posObjIncrust(overlay, i);
return i;
}
}
}
return -1;
}
void Redraw::updateOverlayTypePosition(int16 x1, int16 y1, int16 x2, int16 y2) {
const int16 newX = x2 - x1;
const int16 newY = y2 - y1;
for (int32 i = 0; i < ARRAYSIZE(overlayList); i++) {
OverlayListStruct *overlay = &overlayList[i];
if (overlay->move == OverlayPosType::koFollowActor) {
overlay->x = newX;
overlay->y = newY;
}
}
}
int32 Redraw::fillActorDrawingList(DrawListStruct *drawList, bool flagflip) {
int32 drawListPos = 0;
for (int32 n = 0; n < _engine->_scene->_nbObjets; n++) {
ActorStruct *actor = _engine->_scene->getActor(n);
actor->_workFlags.bWasDrawn = 0; // reset visible state
actor->_workFlags.bIsTargetable = 0;
if (_engine->_grid->_zoneGrm != -1 && actor->_posObj.y > _engine->_scene->_sceneZones[_engine->_grid->_indexGrm].maxs.y) {
continue;
}
// no redraw required
if (actor->_flags.bIsBackgrounded && !flagflip) {
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->posObj() - _engine->_grid->_worldCube);
// check if actor is visible on screen, otherwise don't display it
if (projPos.x > VIEW_X0 && projPos.x < VIEW_X1(_engine) && projPos.y > VIEW_Y0 && projPos.y < VIEW_Y1(_engine)) {
actor->_workFlags.bWasDrawn = 1;
}
continue;
}
// if the actor isn't set as hidden
if (actor->_body == -1 || actor->_flags.bIsInvisible) {
continue;
}
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->posObj() - _engine->_grid->_worldCube);
if ((actor->_flags.bSpriteClip && projPos.x > -112 && projPos.x < _engine->width() + 112 && projPos.y > VIEW_X0 && projPos.y < _engine->height() + 171) ||
((!actor->_flags.bSpriteClip) && projPos.x > VIEW_X0 && projPos.x < VIEW_X1(_engine) && projPos.y > VIEW_Y0 && projPos.y < VIEW_Y1(_engine))) {
int32 ztri = actor->_posObj.x - _engine->_grid->_worldCube.x + actor->_posObj.z - _engine->_grid->_worldCube.z;
// if actor is above another actor
if (actor->_carryBy != -1) {
const ActorStruct *standOnActor = _engine->_scene->getActor(actor->_carryBy);
ztri = standOnActor->_posObj.x - _engine->_grid->_worldCube.x + standOnActor->_posObj.z - _engine->_grid->_worldCube.z + 2;
}
if (actor->_flags.bSprite3D) {
drawList[drawListPos].type = DrawListType::DrawActorSprites;
drawList[drawListPos].numObj = n;
if (actor->_flags.bSpriteClip) {
ztri = actor->_animStep.x - _engine->_grid->_worldCube.x + actor->_animStep.z - _engine->_grid->_worldCube.z;
}
} else {
drawList[drawListPos].type = DrawListType::DrawObject3D;
drawList[drawListPos].numObj = n;
}
drawList[drawListPos].z = ztri;
drawListPos++;
// if use shadows
if (_engine->_cfgfile.ShadowMode != 0 && !(actor->_flags.bNoShadow)) {
if (actor->_carryBy != -1) {
drawList[drawListPos].xw = actor->_posObj.x;
drawList[drawListPos].yw = actor->_posObj.y - 1;
drawList[drawListPos].zw = actor->_posObj.z;
} else {
const IVec3 shadowCoord = _engine->_movements->getShadow(actor->posObj());
drawList[drawListPos].xw = shadowCoord.x;
drawList[drawListPos].yw = shadowCoord.y;
drawList[drawListPos].zw = shadowCoord.z;
}
drawList[drawListPos].z = ztri - 1; // save the shadow entry in the _drawList
drawList[drawListPos].type = DrawListType::DrawShadows;
drawList[drawListPos].numObj = 0;
drawList[drawListPos].num = 1;
drawListPos++;
}
if (_flagMCGA && n == _engine->_scene->_numObjFollow) {
_sceneryViewX = projPos.x;
_sceneryViewY = projPos.y;
}
}
}
return drawListPos;
}
int32 Redraw::fillExtraDrawingList(DrawListStruct *drawList, int32 drawListPos) { // part of AffScene
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_engine->_extra->_extraList[i];
if (extra->sprite == -1) {
continue;
}
if (extra->type & ExtraType::TIME_IN) {
if (_engine->timerRef - extra->spawnTime > 35) {
extra->spawnTime = _engine->timerRef;
extra->type &= ~ExtraType::TIME_IN;
_engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, extra->pos, -1);
}
continue;
}
if ((extra->type & ExtraType::TIME_OUT) && (extra->type & ExtraType::FLASH)) {
if (_engine->timerRef >= extra->spawnTime + extra->payload.lifeTime - _engine->toSeconds(3)) {
if ((_engine->timerRef + extra->spawnTime) & 8) {
continue;
}
}
}
const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
if (projPos.x > VIEW_X0 && projPos.x < VIEW_X1(_engine) && projPos.y > VIEW_Y0 && projPos.y < VIEW_Y1(_engine)) {
const int16 zVal = extra->pos.x - _engine->_grid->_worldCube.x + extra->pos.z - _engine->_grid->_worldCube.z;
drawList[drawListPos].z = zVal;
drawList[drawListPos].numObj = i;
drawList[drawListPos].type = DrawListType::DrawExtras;
drawListPos++;
if (_engine->_cfgfile.ShadowMode == 2 && !(extra->sprite & EXTRA_SPECIAL_MASK)) {
const IVec3 &shadowCoord = _engine->_movements->getShadow(extra->pos);
drawList[drawListPos].z = zVal - 1;
drawList[drawListPos].numObj = 0;
drawList[drawListPos].type = DrawListType::DrawShadows;
drawList[drawListPos].xw = shadowCoord.x;
drawList[drawListPos].yw = shadowCoord.y;
drawList[drawListPos].zw = shadowCoord.z;
drawList[drawListPos].num = 0;
drawListPos++;
}
}
}
return drawListPos;
}
void Redraw::processDrawListShadows(const DrawListStruct &drawCmd) {
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(drawCmd.xw - _engine->_grid->_worldCube.x, drawCmd.yw - _engine->_grid->_worldCube.y, drawCmd.zw - _engine->_grid->_worldCube.z);
int32 spriteWidth = _engine->_resources->_spriteShadowPtr.surface(drawCmd.num).w;
int32 spriteHeight = _engine->_resources->_spriteShadowPtr.surface(drawCmd.num).h;
// calculate sprite size and position on screen
Common::Rect renderRect;
renderRect.left = projPos.x - (spriteWidth / 2);
renderRect.top = projPos.y - (spriteHeight / 2);
renderRect.right = projPos.x + (spriteWidth / 2);
renderRect.bottom = projPos.y + (spriteHeight / 2);
if (_engine->_interface->setClip(renderRect)) {
_engine->_grid->drawSprite(renderRect.left, renderRect.top, _engine->_resources->_spriteShadowPtr, drawCmd.num);
const int32 tmpX = (drawCmd.xw + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
const int32 tmpY = drawCmd.yw / SIZE_BRICK_Y;
const int32 tmpZ = (drawCmd.zw + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick(tmpX, tmpY, tmpZ);
addPhysBox(_engine->_interface->_clip);
_engine->_debugState->drawClip(renderRect);
}
_engine->_interface->unsetClip();
}
void Redraw::processDrawListActors(const DrawListStruct &drawCmd, bool flagflip) {
const int32 actorIdx = drawCmd.numObj;
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_anim >= 0) {
const AnimData &animData = _engine->_resources->_animData[actor->_anim];
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
_engine->_animations->setInterAnimObjet2(actor->_frame, animData, bodyData, &bodyData._animTimerData);
}
const IVec3 &delta = actor->posObj() - _engine->_grid->_worldCube;
Common::Rect renderRect;
if (actorIdx == OWN_ACTOR_SCENE_INDEX) {
if (_engine->_actor->_cropBottomScreen) {
_engine->_interface->_clip.bottom = _engine->_actor->_cropBottomScreen;
}
}
if (!_engine->_renderer->affObjetIso(delta.x, delta.y, delta.z, LBAAngles::ANGLE_0, actor->_beta, LBAAngles::ANGLE_0, actor->_entityDataPtr->getBody(actor->_body), renderRect)) {
return;
}
if (_engine->_interface->setClip(renderRect)) {
actor->_workFlags.bWasDrawn = 1;
const int32 tempX = (actor->_posObj.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
int32 tempY = actor->_posObj.y / SIZE_BRICK_Y;
const int32 tempZ = (actor->_posObj.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
if (actor->brickShape() != ShapeType::kNone) {
tempY++;
}
_engine->_grid->drawOverBrick(tempX, tempY, tempZ);
addPhysBox(_engine->_interface->_clip);
if (actor->_flags.bIsBackgrounded && flagflip) {
_engine->copyBlock(_engine->_interface->_clip);
}
_engine->_debugState->drawClip(_engine->_interface->_clip);
}
_engine->_interface->unsetClip();
}
void Redraw::processDrawListActorSprites(const DrawListStruct &drawCmd, bool bgRedraw) {
int32 actorIdx = drawCmd.numObj;
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const SpriteData &spriteData = _engine->_resources->_spriteData[actor->_body];
// TODO: using the raw pointer and not the SpriteData surface here is a workaround for issue https://bugs.scummvm.org/ticket/12024
const uint8 *spritePtr = _engine->_resources->_spriteTable[actor->_body];
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->posObj() - _engine->_grid->_worldCube);
const int32 dx = spriteData.surface().w;
const int32 dy = spriteData.surface().h;
// calculate sprite position on screen
const SpriteDim *dim = _engine->_resources->_spriteBoundingBox.dim(actor->_body);
Common::Rect renderRect;
renderRect.left = projPos.x + dim->x;
renderRect.top = projPos.y + dim->y;
renderRect.right = renderRect.left + dx;
renderRect.bottom = renderRect.top + dy;
bool validClip;
if (actor->_flags.bSpriteClip) {
const Common::Rect rect(_projPosScreen.x + actor->_cropLeft, _projPosScreen.y + actor->_cropTop, _projPosScreen.x + actor->_cropRight, _projPosScreen.y + actor->_cropBottom);
validClip = _engine->_interface->setClip(rect);
} else {
validClip = _engine->_interface->setClip(renderRect);
}
if (validClip) {
_engine->_grid->drawGraph(0, renderRect.left, renderRect.top, spritePtr);
actor->_workFlags.bWasDrawn = 1;
if (actor->_flags.bSpriteClip) {
const int32 xm = (actor->_animStep.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
const int32 ym = actor->_animStep.y / SIZE_BRICK_Y;
const int32 zm = (actor->_animStep.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick3(xm, ym, zm);
} else {
const int32 xm = (actor->_posObj.x + actor->_boundingBox.maxs.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
int32 ym = actor->_posObj.y / SIZE_BRICK_Y;
const int32 zm = (actor->_posObj.z + actor->_boundingBox.maxs.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
if (actor->brickShape() != ShapeType::kNone) {
ym++;
}
_engine->_grid->drawOverBrick3(xm, ym, zm);
}
addPhysBox(_engine->_interface->_clip);
if (actor->_flags.bIsBackgrounded && bgRedraw) {
_engine->copyBlock(_engine->_interface->_clip);
}
_engine->_debugState->drawClip(renderRect);
_engine->_interface->unsetClip();
}
}
void Redraw::processDrawListExtras(const DrawListStruct &drawCmd) {
int32 extraIdx = drawCmd.numObj;
ExtraListStruct *extra = &_engine->_extra->_extraList[extraIdx];
const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
Common::Rect renderRect;
if (extra->sprite & EXTRA_SPECIAL_MASK) {
_engine->_extra->affSpecial(extraIdx, projPos.x, projPos.y, renderRect);
} else {
const SpriteData &spritePtr = _engine->_resources->_spriteData[extra->sprite];
const int32 dx = spritePtr.surface().w;
const int32 dy = spritePtr.surface().h;
// calculate sprite position on screen
const SpriteDim *dim = _engine->_resources->_spriteBoundingBox.dim(extra->sprite);
renderRect.left = projPos.x + dim->x;
renderRect.top = projPos.y + dim->y;
renderRect.right = renderRect.left + dx;
renderRect.bottom = renderRect.top + dy;
_engine->_grid->drawSprite(renderRect.left, renderRect.top, spritePtr);
}
if (_engine->_interface->setClip(renderRect)) {
const int32 xm = (extra->pos.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
const int32 ym = extra->pos.y / SIZE_BRICK_Y;
const int32 zm = (extra->pos.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick(xm, ym, zm);
addPhysBox(_engine->_interface->_clip);
// show clipping area
//drawRectBorders(renderRect);
_engine->_interface->unsetClip();
}
}
void Redraw::correctZLevels(DrawListStruct *listTri, int32 drawListPos) {
ActorStruct *ptrobj = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
if (ptrobj->_flags.bIsInvisible || ptrobj->_body == -1) {
return;
}
IVec3 tmin = ptrobj->posObj() + ptrobj->_boundingBox.mins;
IVec3 tmax = ptrobj->posObj() + ptrobj->_boundingBox.maxs;
int32 twinsenpos = -1;
int32 twinsenz = -1;
for (int32 pos = 0; pos < drawListPos; ++pos) {
DrawListStruct &drawCmd = listTri[pos];
if (drawCmd.type == DrawListType::DrawObject3D && drawCmd.numObj == OWN_ACTOR_SCENE_INDEX) {
twinsenpos = pos;
twinsenz = drawCmd.z;
break;
}
}
if (twinsenpos == -1) {
return;
}
for (int32 n = 0; n < drawListPos; ++n) {
DrawListStruct &ptrtri = listTri[n];
uint32 typeobj = ptrtri.type;
int32 numobj = ptrtri.numObj;
ptrobj = _engine->_scene->getActor(numobj);
switch (typeobj) {
default:
break;
case DrawListType::DrawActorSprites:
if (ptrobj->_flags.bSpriteClip) {
IVec3 pmin = ptrobj->_animStep + ptrobj->_boundingBox.mins;
IVec3 pmax = ptrobj->_animStep + ptrobj->_boundingBox.maxs;
if (pmax.x > tmin.x && pmin.x < tmax.x) {
if (pmax.z <= tmin.z) {
// twinsen after
if (twinsenz < ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
}
if (pmin.z >= tmax.z) {
// twinsen before
if (twinsenz > ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
}
break;
}
if (pmax.z > tmin.z && pmin.z < tmax.z) {
if (pmax.x <= tmin.x) {
// twinsen after
if (twinsenz < ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
} else {
// twinsen before
if (twinsenz > ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
}
}
}
break;
}
if (numobj == -1) {
break;
}
}
}
void Redraw::processDrawList(DrawListStruct *drawList, int32 drawListPos, bool bgRedraw) {
bool shadowtwinsen = false;
for (int32 pos = 0; pos < drawListPos; ++pos) {
const DrawListStruct &drawCmd = drawList[pos];
const uint32 flags = drawCmd.type;
if (flags == DrawListType::DrawObject3D) {
// this is correcting a bug that came with correctZLevels() - original sources
if (_engine->_cfgfile.ShadowMode != 0 && drawCmd.numObj == OWN_ACTOR_SCENE_INDEX && !shadowtwinsen) {
for (int32 i = pos; i < drawListPos; i++) {
if (drawList[i].numObj == OWN_ACTOR_SCENE_INDEX && drawList[i].type == DrawListType::DrawShadows) {
shadowtwinsen = true;
processDrawListShadows(drawList[i]);
drawList[i].type = (uint32)-1; // invalidate shadow entry
break;
}
}
}
processDrawListActors(drawCmd, bgRedraw);
} else if (flags == DrawListType::DrawShadows && !_engine->_actor->_cropBottomScreen) {
if (drawCmd.numObj == OWN_ACTOR_SCENE_INDEX) {
shadowtwinsen = true;
}
processDrawListShadows(drawCmd);
} else if (flags == DrawListType::DrawActorSprites) {
processDrawListActorSprites(drawCmd, bgRedraw);
} else if (flags == DrawListType::DrawExtras) {
processDrawListExtras(drawCmd);
}
_engine->_interface->unsetClip();
}
}
void Redraw::renderOverlays() {
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
OverlayListStruct *overlay = &overlayList[i];
if (overlay->num == -1) {
continue;
}
// process position overlay
switch (overlay->move) {
case OverlayPosType::koNormal: // wait number of seconds and die
if (_engine->timerRef >= overlay->timerEnd) {
overlay->num = -1;
continue;
}
break;
case OverlayPosType::koFollowActor: { // follow obj coordinates for number of seconds and die
ActorStruct *actor2 = _engine->_scene->getActor(overlay->info);
const IVec3 &projPos = _engine->_renderer->projectPoint(actor2->_posObj.x - _engine->_grid->_worldCube.x, actor2->_posObj.y + actor2->_boundingBox.maxs.y - _engine->_grid->_worldCube.y, actor2->_posObj.z - _engine->_grid->_worldCube.z);
overlay->x = projPos.x;
overlay->y = projPos.y;
if (_engine->timerRef >= overlay->timerEnd) {
overlay->num = -1;
continue;
}
break;
}
}
// process overlay type
switch (overlay->type) {
case OverlayType::koSprite: {
const SpriteData &spritePtr = _engine->_resources->_spriteData[overlay->num];
const int32 spriteWidth = spritePtr.surface().w;
const int32 spriteHeight = spritePtr.surface().h;
const SpriteDim *dim = _engine->_resources->_spriteBoundingBox.dim(overlay->num);
Common::Rect renderRect;
renderRect.left = dim->x + overlay->x;
renderRect.top = dim->y + overlay->y;
renderRect.right = renderRect.left + spriteWidth;
renderRect.bottom = renderRect.top + spriteHeight;
_engine->_grid->drawSprite(renderRect.left, renderRect.top, spritePtr);
addPhysBox(_engine->_interface->_clip);
break;
}
case OverlayType::koNumber: {
char text[10];
snprintf(text, sizeof(text), "%d", overlay->num);
const int32 textLength = _engine->_text->sizeFont(text);
const int32 textHeight = 48;
Common::Rect renderRect;
renderRect.left = overlay->x - (textLength / 2);
renderRect.top = overlay->y - 24;
renderRect.right = overlay->x + (textLength / 2);
renderRect.bottom = overlay->y + textHeight;
_engine->_interface->setClip(renderRect);
_engine->_text->setFontColor(overlay->info);
_engine->_text->drawText(renderRect.left, renderRect.top, text);
addPhysBox(_engine->_interface->_clip);
_engine->_interface->unsetClip();
break;
}
case OverlayType::koNumberRange: {
const int32 range = boundRuleThree(overlay->info, overlay->num, 100, overlay->timerEnd - _engine->timerRef - _engine->toSeconds(1));
char text[10];
Common::sprintf_s(text, "%d", range);
const int32 textLength = _engine->_text->sizeFont(text);
const int32 textHeight = 48;
Common::Rect renderRect;
renderRect.left = overlay->x - (textLength / 2);
renderRect.top = overlay->y - 24;
renderRect.right = overlay->x + (textLength / 2);
renderRect.bottom = overlay->y + textHeight;
_engine->_interface->setClip(renderRect);
_engine->_text->setFontColor(COLOR_GOLD);
_engine->_text->drawText(renderRect.left, renderRect.top, text);
addPhysBox(_engine->_interface->_clip);
_engine->_interface->unsetClip();
break;
}
case OverlayType::koInventoryItem: {
const int32 item = overlay->num;
const Common::Rect rect(10, 10, 79, 79);
_engine->_interface->box(rect, COLOR_BLACK);
_engine->_interface->setClip(rect);
const BodyData &bodyPtr = _engine->_resources->_inventoryTable[item];
_overlayRotation += 1; // overlayRotation += 8;
_engine->_renderer->draw3dObject(40, 40, bodyPtr, _overlayRotation, 16000);
_engine->_menu->drawRectBorders(rect);
addPhysBox(rect);
_engine->_gameState->init3DGame();
_engine->_interface->unsetClip();
break;
}
case OverlayType::koText: {
char text[256];
_engine->_text->getMenuText((TextId)overlay->num, text, sizeof(text));
const int32 textLength = _engine->_text->sizeFont(text);
const int32 textHeight = 48;
Common::Rect renderRect;
renderRect.left = overlay->x - (textLength / 2);
renderRect.top = overlay->y - (textHeight / 2);
renderRect.right = overlay->x + (textLength / 2);
renderRect.bottom = overlay->y + textHeight;
renderRect.clip(_engine->rect());
_engine->_interface->setClip(renderRect);
_engine->_text->setFontColor(_engine->_scene->getActor(overlay->info)->_talkColor);
_engine->_text->drawText(renderRect.left, renderRect.top, text);
addPhysBox(_engine->_interface->_clip);
_engine->_interface->unsetClip();
break;
}
case OverlayType::koSysText:
case OverlayType::koFlash:
case OverlayType::koRain:
case OverlayType::koInventory:
// TODO lba2
case OverlayType::koMax:
break;
}
}
}
void Redraw::setRenderText(const Common::String &text) {
_text = text;
if (_text.empty()) {
_textDisappearTime = -1;
} else {
_textDisappearTime = _engine->timerRef + _engine->toSeconds(1);
}
}
void Redraw::renderText() {
if (_text.empty()) {
return;
}
if (_textDisappearTime != -1 && _engine->timerRef > _textDisappearTime) {
_text.clear();
_textDisappearTime = -1;
return;
}
_engine->_text->setFontColor(COLOR_WHITE);
const int padding = 10;
const int x = padding;
const int height = _engine->_text->lineHeight;
const int y = _engine->height() - height - padding;
const int width = _engine->_text->sizeFont(_text.c_str());
_engine->_text->drawText(x, y, _text.c_str(), true);
_engine->copyBlockPhys(x, y, x + width, y + height);
const Common::Rect redraw(x, y, x + width, y + height);
addPhysBox(redraw);
}
void Redraw::fillBackground(uint8 color) {
_engine->_frontVideoBuffer.fillRect(Common::Rect(0, 0, _engine->width(), _engine->height()), color);
}
void Redraw::drawScene(bool flagflip) { // AffScene
int32 tmp_projPosX = _projPosScreen.x;
int32 tmp_projPosY = _projPosScreen.y;
_engine->_interface->unsetClip();
if (!flagflip) {
clsBoxes();
} else {
_engine->saveTimer(false);
if (_engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_1 && _engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_2) {
_engine->_screens->fadeToBlack(_engine->_screens->_ptrPal);
}
_engine->_grid->redrawGrid();
updateOverlayTypePosition(tmp_projPosX, tmp_projPosY, _projPosScreen.x, _projPosScreen.y);
_engine->saveFrontBuffer();
if (_engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_1 && _engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_2) {
_engine->_screens->fadeToPal(_engine->_screens->_ptrPal);
}
}
DrawListStruct drawList[NUM_MAX_ACTORS + EXTRA_MAX_ENTRIES]; // ListTri[MAX_OBJECTS + MAX_EXTRAS]
int32 drawListPos = fillActorDrawingList(drawList, flagflip);
drawListPos = fillExtraDrawingList(drawList, drawListPos);
_nbPhysBox = 0;
sortDrawingList(drawList, drawListPos);
correctZLevels(drawList, drawListPos);
processDrawList(drawList, drawListPos, flagflip);
_engine->_debugState->renderDebugView();
renderOverlays();
renderText();
_engine->_interface->unsetClip();
// need to be here to fade after drawing all actors in scene
if (_engine->_scene->_newCube == SCENE_CEILING_GRID_FADE_2) {
_engine->_scene->_newCube = SCENE_CEILING_GRID_FADE_1;
}
if (flagflip) {
moveNextAreas();
_engine->restoreTimer();
} else {
flipBoxes();
}
if (_engine->_screens->_flagFade) {
if (_engine->_screens->_flagPalettePcx) {
_engine->_screens->fadeToPal(_engine->_screens->_palettePcx);
} else {
_engine->_screens->fadeToPal(_engine->_screens->_ptrPal);
}
_engine->_screens->_flagFade = false;
}
}
void Redraw::drawBubble(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->_posObj.x - _engine->_grid->_worldCube.x, actor->_posObj.y + actor->_boundingBox.maxs.y - _engine->_grid->_worldCube.y, actor->_posObj.z - _engine->_grid->_worldCube.z);
if (actorIdx != _bubbleActor) {
_bubbleSpriteIndex = _bubbleSpriteIndex ^ 1;
_bubbleActor = actorIdx;
}
const SpriteData &spritePtr = _engine->_resources->_spriteData[_bubbleSpriteIndex];
const int32 spriteWidth = spritePtr.surface().w;
const int32 spriteHeight = spritePtr.surface().h;
// calculate sprite position on screen
Common::Rect renderRect;
if (_bubbleSpriteIndex == SPRITEHQR_DIAG_BUBBLE_RIGHT) {
renderRect.left = projPos.x + 10;
} else {
renderRect.left = projPos.x - 10 - spriteWidth;
}
renderRect.top = projPos.y - 20;
renderRect.right = spriteWidth + renderRect.left - 1;
renderRect.bottom = spriteHeight + renderRect.top - 1;
if (_engine->_interface->setClip(renderRect)) {
_engine->_grid->drawSprite(renderRect.left, renderRect.top, spritePtr);
_engine->_interface->unsetClip();
}
}
} // namespace TwinE

View File

@@ -0,0 +1,210 @@
/* 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 TWINE_RENDERER_REDRAW_H
#define TWINE_RENDERER_REDRAW_H
#include "common/scummsys.h"
#include "common/rect.h"
#include "common/str.h"
#include "twine/shared.h"
namespace TwinE {
// MAX_INCRUST_DISP
#define OVERLAY_MAX_ENTRIES 10
enum class OverlayType {
koSprite = 0, // INCRUST_SPRITE
koNumber = 1, // INCRUST_NUM
koNumberRange = 2, // INCRUST_CMPT
koInventoryItem = 3, // INCRUST_OBJ
koText = 4, // INCRUST_TEXT
koInventory = 5, // lba2 (INCRUST_INVENTORY)
koSysText = 6, // lba2 (INCRUST_SYS_TEXT)
koFlash = 7, // lba2 (INCRUST_ECLAIR)
koRain = 8, // lba2 (INCRUST_PLUIE)
koMax
};
// lba2
#define INCRUST_YCLIP (1 << 8)
enum class OverlayPosType {
koNormal = 0,
koFollowActor = 1
};
/** Overlay list structure */
struct OverlayListStruct {
int16 num = 0; // sprite/3d model entry | number | number range
int16 x = 0;
int16 y = 0;
OverlayType type = OverlayType::koSprite;
int16 info = 0; // text = actor | total coins
OverlayPosType move = OverlayPosType::koNormal;
int16 timerEnd = 0; // life time in ticks - see toSeconds()
};
struct DrawListStruct {
int16 z = 0; // depth sorting value
uint32 type = 0;
// NumObj was also used with mask of type and numObj - we are
// not masking the value in numObj, but store the type in type
uint16 numObj = 0;
uint16 xw = 0;
uint16 yw = 0;
uint16 zw = 0;
uint16 num = 0;
inline bool operator==(const DrawListStruct& other) const {
return z == other.z;
}
inline bool operator<(const DrawListStruct& other) const {
return z < other.z;
}
};
#define TYPE_OBJ_SHIFT (10)
#define TYPE_OBJ_FIRST (1 << TYPE_OBJ_SHIFT) // 1024
#define NUM_OBJ_MASK (TYPE_OBJ_FIRST - 1)
class TwinEEngine;
class Redraw {
private:
TwinEEngine *_engine;
enum DrawListType {
DrawObject3D = (0 << TYPE_OBJ_SHIFT), // TYPE_OBJ_3D
DrawFlagRed = (1 << TYPE_OBJ_SHIFT),
DrawFlagYellow = (2 << TYPE_OBJ_SHIFT),
DrawShadows = (3 << TYPE_OBJ_SHIFT), // TYPE_SHADOW
DrawActorSprites = (4 << TYPE_OBJ_SHIFT), // TYPE_OBJ_SPRITE
DrawZoneDec = (5 << TYPE_OBJ_SHIFT),
DrawExtras = (6 << TYPE_OBJ_SHIFT), // TYPE_EXTRA
DrawPrimitive = (7 << TYPE_OBJ_SHIFT)
};
Common::Rect _currentRedrawList[300];
Common::Rect _nextRedrawList[300];
int16 _overlayRotation = 0;
/** Save last actor that bubble dialog icon */
int32 _bubbleActor = -1;
int32 _bubbleSpriteIndex;
// big font shadow text in the lower left corner
Common::String _text;
int32 _textDisappearTime = -1;
/**
* Add a certain region to the current redraw list array
* @param redrawArea redraw the region
*/
void addRedrawCurrentArea(const Common::Rect &redrawArea);
/**
* Move next regions to the current redraw list,
* setup the redraw areas for next display
*/
void moveNextAreas();
void updateOverlayTypePosition(int16 x1, int16 y1, int16 x2, int16 y2);
void processDrawListShadows(const DrawListStruct& drawCmd);
void processDrawListActors(const DrawListStruct& drawCmd, bool bgRedraw);
void processDrawListActorSprites(const DrawListStruct& drawCmd, bool bgRedraw);
void processDrawListExtras(const DrawListStruct& drawCmd);
int32 fillActorDrawingList(DrawListStruct *drawList, bool bgRedraw);
int32 fillExtraDrawingList(DrawListStruct *drawList, int32 drawListPos);
void correctZLevels(DrawListStruct *drawList, int32 drawListPos);
void processDrawList(DrawListStruct *drawList, int32 drawListPos, bool bgRedraw);
void renderOverlays();
void renderText();
void fillBackground(uint8 color);
public:
Redraw(TwinEEngine *engine);
bool _flagMCGA = false;
/** Request background redraw */
bool _firstTime = false;
IVec3 _projPosScreen; // XpOrgw, YpOrgw
/** Current number of redraw regions in the screen */
int32 _nbPhysBox = 0; // fullRedrawVar8
/** Number of redraw regions in the screen */
int32 _nbOptPhysBox = 0;
int _sceneryViewX = 0; // xmin
int _sceneryViewY = 0; // ymin
OverlayListStruct overlayList[OVERLAY_MAX_ENTRIES];
void setRenderText(const Common::String &text);
// InitIncrustDisp
int32 addOverlay(OverlayType type, int16 info0, int16 x, int16 y, int16 info1, OverlayPosType posType, int16 lifeTime);
void posObjIncrust(OverlayListStruct *ptrdisp, int32 num); // lba2
/**
* Add a certain region to redraw list array
* @param left start width to redraw the region
* @param top start height to redraw the region
* @param right end width to redraw the region
* @param bottom end height to redraw the region
*/
void addRedrawArea(int32 left, int32 top, int32 right, int32 bottom); // AddPhysBox
void addPhysBox(const Common::Rect &rect); // AddPhysBox
/**
* Flip currentRedrawList regions in the screen
* This only updates small areas in the screen so few CPU processor is used
*/
void flipBoxes();
/** Blit/Update all screen regions in the currentRedrawList */
void clsBoxes();
/**
* This is responsible for the entire game screen redraw
* @param bgRedraw true if we want to redraw background grid, false if we want to update certain screen areas
*/
void drawScene(bool bgRedraw);
/** Draw dialogue sprite image */
void drawBubble(int32 actorIdx);
/**
* Sort drawing list struct ordered as the first objects appear in the top left corner of the screen
* @param list drawing list variable which contains information of the drawing objects
* @param listSize number of drawing objects in the list
*/
void sortDrawingList(DrawListStruct *list, int32 listSize) const;
};
} // namespace TwinE
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,326 @@
/* 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 TWINE_RENDERER_RENDERER_H
#define TWINE_RENDERER_RENDERER_H
#include "common/endian.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "twine/parser/bodytypes.h"
#include "twine/twine.h"
#define POLYGONTYPE_FLAT 0
#define POLYGONTYPE_TELE 1
// horizontal color adjustment with changing pattern over the polygon
#define POLYGONTYPE_COPPER 2
#define POLYGONTYPE_BOPPER 3
#define POLYGONTYPE_MARBLE 4
#define POLYGONTYPE_TRANS 5
#define POLYGONTYPE_TRAME 6
#define POLYGONTYPE_GOURAUD 7
#define POLYGONTYPE_DITHER 8
#define POLYGONTYPE_OUTLINE 9
#define MAT_TRISTE 0
#define MAT_PIERRE 1
#define MAT_COPPER 2
#define MAT_BOPPER 3
#define MAT_MARBRE 4
#define MAT_TRANS 5
#define MAT_TRAME 6
#define MAT_FLAT 7
#define MAT_GRANIT 8
#define MAT_GOURAUD 9
#define MAT_DITHER 10
#define TYPE_3D 0
#define TYPE_ISO 1
namespace TwinE {
class BodyData;
class TwinEEngine;
struct ComputedVertex {
int16 intensity = 0;
int16 x = 0;
int16 y = 0;
};
bool isPolygonVisible(const ComputedVertex *vertices);
struct CmdRenderPolygon {
uint8 renderType = 0;
uint8 numVertices = 0;
int16 colorIndex = 0; // intensity
// followed by Vertex array
};
struct IMatrix3x3 {
IVec3 row1;
IVec3 row2;
IVec3 row3;
};
inline IMatrix3x3 operator*(const IMatrix3x3 &matrix, const IVec3 &vec) {
IMatrix3x3 out;
out.row1.x = matrix.row1.x * vec.x;
out.row1.y = matrix.row1.y * vec.x;
out.row1.z = matrix.row1.z * vec.x;
out.row2.x = matrix.row2.x * vec.y;
out.row2.y = matrix.row2.y * vec.y;
out.row2.z = matrix.row2.z * vec.y;
out.row3.x = matrix.row3.x * vec.z;
out.row3.y = matrix.row3.y * vec.z;
out.row3.z = matrix.row3.z * vec.z;
return out;
}
class Renderer {
private:
TwinEEngine *_engine;
struct RenderCommand {
int16 depth = 0;
int16 renderType = 0;
/**
* Pointer to the command data
* @sa renderCoordinatesBuffer
* @sa CmdRenderLine
* @sa CmdRenderSphere
* @sa CmdRenderPolygon
*/
uint8 *dataPtr = nullptr;
};
/**
* @brief A render command payload for drawing a line
*
* @sa RenderCommand
*/
struct CmdRenderLine {
uint8 colorIndex = 0;
uint8 unk1 = 0;
uint8 unk2 = 0;
uint8 unk3 = 0;
int16 x1 = 0;
int16 y1 = 0;
int16 x2 = 0;
int16 y2 = 0;
};
/**
* @brief A render command payload for drawing a sphere
*
* @sa RenderCommand
*/
struct CmdRenderSphere {
uint16 color = 0; // color start and end values
uint8 polyRenderType = 0;
int16 radius = 0;
int16 x = 0;
int16 y = 0;
int16 z = 0;
};
struct ModelData {
I16Vec3 computedPoints[800]; // List_Anim_Point
I16Vec3 flattenPoints[800];
int16 normalTable[500]{0};
};
// this is a member var, because 10k on the stack is not supported by every platform
ModelData _modelData;
// AnimNuage
void animModel(ModelData *modelData, const BodyData &bodyData, RenderCommand *renderCmds, const IVec3 &angleVec, const IVec3 &renderPos, Common::Rect &modelRect);
bool computeSphere(int32 x, int32 y, int32 radius, int &vtop, int &vbottom);
bool renderObjectIso(const BodyData &bodyData, RenderCommand **renderCmds, ModelData *modelData, Common::Rect &modelRect); // RenderObjetIso
IVec3 longInverseRot(int32 x, int32 y, int32 z);
inline IVec3 getCameraAnglePositions(const IVec3 &vec) {
return longInverseRot(vec.x, vec.y, vec.z);
}
void rotMatIndex2(IMatrix3x3 *targetMatrix, const IMatrix3x3 *currentMatrix, const IVec3 &angleVec);
void rotList(const Common::Array<BodyVertex>& vertices, int32 firstPoint, int32 numPoints, I16Vec3 *destPoints, const IMatrix3x3 *rotationMatrix, const IVec3 &destPos);
void processRotatedElement(IMatrix3x3 *targetMatrix, const Common::Array<BodyVertex>& vertices, int32 rotX, int32 rotY, int32 rotZ, const BodyBone &bone, ModelData *modelData);
void transRotList(const Common::Array<BodyVertex>& vertices, int32 firstPoint, int32 numPoints, I16Vec3 *destPoints, const IMatrix3x3 *translationMatrix, const IVec3 &angleVec, const IVec3 &destPos);
void translateGroup(IMatrix3x3 *targetMatrix, const Common::Array<BodyVertex>& vertices, int32 rotX, int32 rotY, int32 rotZ, const BodyBone &bone, ModelData *modelData);
/**
* @brief Rotate the given coordinates by the given rotation matrix
*/
IVec3 rot(const IMatrix3x3 &matrix, int32 x, int32 y, int32 z);
IVec3 _cameraPos; // CameraX, CameraY, CameraZ
IVec3 _projectionCenter{320, 200, 0}; // XCentre, YCentre, IsoScale
int32 _kFactor = 128;
int32 _lFactorX = 1024;
int32 _lFactorY = 840;
IMatrix3x3 _matrixWorld; // LMatriceWorld
IMatrix3x3 _matricesTable[30 + 1];
IVec3 _normalLight; // NormalXLight, NormalYLight, NormalZLight
IVec3 _cameraRot; // CameraXr, CameraYr, CameraZr
RenderCommand _renderCmds[1000];
/**
* @brief Raw buffer for holding the render commands. This is a type followed by the command data
* that is needed to render the primitive.
*/
uint8 _renderCoordinatesBuffer[10000]{0};
ComputedVertex _clippedPolygonVertices1[128];
ComputedVertex _clippedPolygonVertices2[128];
int16* _tabVerticG = nullptr;
int16* _tabVerticD = nullptr;
int16* _tabCoulG = nullptr;
int16* _tabCoulD = nullptr;
int16* _taby0 = nullptr;
int16* _taby1 = nullptr;
int16* _tabx0 = nullptr; // also _tabCoulG
int16* _tabx1 = nullptr; // also _tabCoulD
bool _typeProj = TYPE_3D;
void svgaPolyCopper(int16 vtop, int16 vbottom, uint16 color) const;
void svgaPolyBopper(int16 vtop, int16 vbottom, uint16 color) const;
void svgaPolyTriste(int16 vtop, int16 vbottom, uint16 color) const;
void svgaPolyTele(int16 vtop, int16 vbottom, uint16 color) const;
void svgaPolyTrans(int16 vtop, int16 vbottom, uint16 color) const;
void svgaPolyTrame(int16 vtop, int16 vbottom, uint16 color) const;
void svgaPolyGouraud(int16 vtop, int16 vbottom) const;
void svgaPolyDith(int16 vtop, int16 vbottom) const;
void svgaPolyMarbre(int16 vtop, int16 vbottom, uint16 color) const;
void svgaPolyTriche(int16 vtop, int16 vbottom, uint16 color) const;
bool computePoly(int16 polyRenderType, const ComputedVertex *vertices, int32 numVertices, int16 &vtop, int16 &vbottom);
const RenderCommand *depthSortRenderCommands(int32 numOfPrimitives);
uint8 *preparePolygons(const Common::Array<BodyPolygon>& polygons, int32 &numOfPrimitives, RenderCommand **renderCmds, uint8 *renderBufferPtr, ModelData *modelData);
uint8 *prepareSpheres(const Common::Array<BodySphere>& spheres, int32 &numOfPrimitives, RenderCommand **renderCmds, uint8 *renderBufferPtr, ModelData *modelData);
uint8 *prepareLines(const Common::Array<BodyLine>& lines, int32 &numOfPrimitives, RenderCommand **renderCmds, uint8 *renderBufferPtr, ModelData *modelData);
void flipMatrix();
void fillTextPolyNoClip(int32 top, int32 bottom, const uint8 *holomapImage, uint32 holomapImageSize);
void fillHolomapTriangle(int16 *pDest, int32 x1, int32 y1, int32 x2, int32 y2);
void fillHolomapTriangles(const ComputedVertex &vertex1, const ComputedVertex &vertex2, const ComputedVertex &texCoord1, const ComputedVertex &texCoord2, int32 &top, int32 &bottom);
// ClipGauche, ClipDroite, ClipHaut, ClipBas
int16 leftClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices);
int16 rightClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices);
int16 topClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices);
int16 bottomClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices);
int32 computePolyMinMax(int16 polyRenderType, ComputedVertex **offTabPoly, int32 numVertices, int16 &vtop, int16 &vbottom);
public:
Renderer(TwinEEngine *engine);
~Renderer();
void init(int32 w, int32 h);
void setCameraRotation(int32 x, int32 y, int32 z);
/**
* Calculate offset for the side and forward distances by the given angle of an actor
* @param side Actor current X coordinate
* @param forward Actor current Z coordinate
* @param angle Actor angle to rotate
*/
IVec2 rotate(int32 side, int32 forward, int32 angle) const;
void setLightVector(int32 angleX, int32 angleY, int32 angleZ);
IVec3 longWorldRot(int32 x, int32 y, int32 z);
IVec3 worldRotatePoint(const IVec3& vec);
void fillVertices(int16 vtop, int16 vbottom, uint8 renderType, uint16 color);
void renderPolygons(const CmdRenderPolygon &polygon, ComputedVertex *vertices);
inline IVec3 projectPoint(const IVec3& pos) { // ProjettePoint
return projectPoint(pos.x, pos.y, pos.z);
}
void projIso(IVec3 &pos, int32 x, int32 y, int32 z);
IVec3 projectPoint(int32 cX, int32 cY, int32 cZ);
void setFollowCamera(int32 transPosX, int32 transPosY, int32 transPosZ, int32 cameraAlpha, int32 cameraBeta, int32 cameraGamma, int32 cameraZoom);
void setPosCamera(int32 x, int32 y, int32 z);
IVec3 setAngleCamera(int32 alpha, int32 beta, int32 gamma);
IVec3 setInverseAngleCamera(int32 alpha, int32 beta, int32 gamma);
inline IVec3 setBaseRotation(const IVec3 &rot) {
return setAngleCamera(rot.x, rot.y, rot.z);
}
void setProjection(int32 x, int32 y, int32 depthOffset, int32 scaleX, int32 scaleY);
void setIsoProjection(int32 x, int32 y, int32 scale);
bool affObjetIso(int32 x, int32 y, int32 z, int32 angleX, int32 angleY, int32 angleZ, const BodyData &bodyData, Common::Rect &modelRect);
inline bool renderIsoModel(const IVec3 &pos, int32 angleX, int32 angleY, int32 angleZ, const BodyData &bodyData, Common::Rect &modelRect) {
return affObjetIso(pos.x, pos.y, pos.z, angleX, angleY, angleZ, bodyData, modelRect);
}
/**
* @param angle A value of @c -1 means that the model is automatically rotated
*/
void renderBehaviourModel(int32 boxLeft, int32 boxTop, int32 boxRight, int32 boxBottom, int32 y, int32 angle, const BodyData &bodyData, RealValue &move);
/**
* @param angle A value of @c -1 means that the model is automatically rotated
*/
void drawObj3D(const Common::Rect &rect, int32 y, int32 angle, const BodyData &bodyData, RealValue &move);
/**
* @brief Render an inventory item
*/
void draw3dObject(int32 x, int32 y, const BodyData &bodyData, int32 angle, int32 cameraZoom);
void asmTexturedTriangleNoClip(const ComputedVertex vertexCoordinates[3], const ComputedVertex textureCoordinates[3], const uint8 *holomapImage, uint32 holomapImageSize);
inline IVec3 getCameraPosition() const {
return _cameraPos;
}
inline IVec3 getCameraRotation() const {
return _cameraRot;
}
inline int32 getLFactorX() const {
return _lFactorX;
}
inline int32 getLFactorY() const {
return _lFactorY;
}
};
inline void Renderer::setCameraRotation(int32 x, int32 y, int32 z) {
_cameraRot.x = x;
_cameraRot.y = y;
_cameraRot.z = z;
}
} // namespace TwinE
#endif

View File

@@ -0,0 +1,287 @@
/* 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 "twine/renderer/screens.h"
#include "common/file.h"
#include "common/str.h"
#include "common/system.h"
#include "common/util.h"
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
#include "graphics/pixelformat.h"
#include "graphics/surface.h"
#include "image/bmp.h"
#include "image/image_decoder.h"
#include "image/png.h"
#include "twine/audio/music.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/shared.h"
#include "twine/twine.h"
namespace TwinE {
int32 Screens::mapLba2Palette(int32 palIndex) {
static const int32 palettes[] {
RESSHQR_MAINPAL,
-1, // TODO: current palette
RESSHQR_BLACKPAL,
RESSHQR_ECLAIRPAL
};
if (palIndex < 0 || palIndex >= ARRAYSIZE(palettes)) {
return -1;
}
return palettes[palIndex];
}
bool Screens::adelineLogo() {
_engine->_music->playMidiFile(31);
// TODO: whiteFade and fadeWhiteToPal
return loadImageDelay(_engine->_resources->adelineLogo(), 7);
}
void Screens::loadMenuImage(bool fadeIn) {
loadImage(_engine->_resources->menuBackground(), fadeIn);
_engine->_workVideoBuffer.blitFrom(_engine->_frontVideoBuffer);
}
void Screens::loadCustomPalette(const TwineResource &resource) {
if (!HQR::getPaletteEntry(_palettePcx, resource)) {
error("Failed to get palette entry for custom palette: %s:%d", resource.hqr, resource.index);
}
if (_palettePcx.size() != NUMOFCOLORS) {
warning("Unexpected palette size %s:%i", resource.hqr, resource.index);
}
}
void Screens::loadImage(TwineImage image, bool fadeIn) {
Graphics::ManagedSurface& src = _engine->_imageBuffer;
if (HQR::getEntry((uint8 *)src.getPixels(), image.image) == 0) {
warning("Failed to load image with index %i", image.image.index);
return;
}
debugC(1, TwinE::kDebugResources, "Load image: %i", image.image.index);
Graphics::ManagedSurface& target = _engine->_frontVideoBuffer;
target.blitFrom(src, src.getBounds(), target.getBounds());
const Graphics::Palette *pal = &_ptrPal;
if (image.palette.index != -1) {
loadCustomPalette(image.palette);
pal = &_palettePcx;
}
if (fadeIn) {
fadeToPal(*pal);
} else {
_engine->setPalette(*pal);
}
}
bool Screens::loadImageDelay(TwineImage image, int32 seconds) {
loadImage(image);
if (_engine->delaySkip(1000 * seconds)) {
fadePal(0, 0, 0, _palettePcx, 100);
return true;
}
fadeToBlack(_palettePcx);
return false;
}
template<class ImageDecoder>
static bool loadImageDelayViaDecoder(TwinEEngine *engine, const Common::Path &fileName, int32 seconds) {
ImageDecoder decoder;
Common::File fileHandle;
if (!fileHandle.open(fileName)) {
warning("Failed to open %s", fileName.toString().c_str());
return false;
}
if (!decoder.loadStream(fileHandle)) {
warning("Failed to load %s", fileName.toString().c_str());
return false;
}
const Graphics::Surface *src = decoder.getSurface();
if (src == nullptr) {
warning("Failed to decode %s", fileName.toString().c_str());
return false;
}
Graphics::ManagedSurface &target = engine->_frontVideoBuffer;
Common::Rect rect(src->w, src->h);
if (!decoder.hasPalette()) {
uint8 pal[Graphics::PALETTE_SIZE];
engine->_frontVideoBuffer.getPalette(pal, 0, 256);
Graphics::Surface *source = decoder.getSurface()->convertTo(target.format, nullptr, 0, pal, 256);
target.blitFrom(*source, rect, target.getBounds());
source->free();
delete source;
} else {
engine->setPalette(0, decoder.getPalette().size(), decoder.getPalette().data());
target.blitFrom(*src, rect, target.getBounds());
}
if (engine->delaySkip(1000 * seconds)) {
return true;
}
return false;
}
bool Screens::loadBitmapDelay(const char *image, int32 seconds) {
Common::Path path(image);
Common::String filename = path.baseName();
size_t extPos = filename.rfind(".");
if (extPos == Common::String::npos) {
warning("Failed to extract extension %s", image);
return false;
}
struct ImageLoader {
const char *extension;
bool (*loadImageDelay)(TwinEEngine *engine, const Common::Path &fileName, int32 seconds);
};
static const ImageLoader s_imageLoaders[] = {
{ "bmp", loadImageDelayViaDecoder<Image::BitmapDecoder> },
{ "png", loadImageDelayViaDecoder<Image::PNGDecoder> },
{ nullptr, nullptr }
};
const Common::String &ext = filename.substr(extPos + 1);
for (const ImageLoader *loader = s_imageLoaders; loader->extension; ++loader) {
if (!scumm_stricmp(loader->extension, ext.c_str())) {
return loader->loadImageDelay(_engine, path, seconds);
}
}
warning("Failed to find suitable image handler %s", image);
return false;
}
void Screens::fadePal(uint8 r, uint8 g, uint8 b, const Graphics::Palette &rgbaPal, int32 intensity) {
Graphics::Palette pal{NUMOFCOLORS};
for (int32 i = 0; i < NUMOFCOLORS; i++) {
byte rIn, gIn, bIn;
rgbaPal.get(i, rIn, gIn, bIn);
const byte newR = ruleThree32(r, rIn, 100, intensity);
const byte newG = ruleThree32(g, gIn, 100, intensity);
const byte newB = ruleThree32(b, bIn, 100, intensity);
pal.set(i, newR, newG, newB);
}
_engine->setPalette(pal);
_engine->_frontVideoBuffer.update();
}
void Screens::fadeToBlack(const Graphics::Palette &ptrpal) {
if (_flagBlackPal) {
return;
}
for (int32 n = 100; n >= 0; n -= 2) {
FrameMarker frame(_engine, 66); // VSync()
fadePal(0, 0, 0, ptrpal, n);
}
_flagBlackPal = true;
}
void Screens::whiteFade() {
Graphics::Palette workpal{NUMOFCOLORS};
for (int32 n = 0; n <= 255; n++) {
FrameMarker frame(_engine, 66); // VSync()
for (int i = 0; i < NUMOFCOLORS; i++) {
workpal.set(i, n, n, n);
}
_engine->setPalette(workpal);
_engine->_frontVideoBuffer.update();
}
}
void Screens::fadeWhiteToPal(const Graphics::Palette &ptrpal) {
for (int32 n = 0; n <= 100; ++n) {
FrameMarker frame(_engine, 66); // VSync()
fadePal(255, 255, 255, ptrpal, n);
}
}
void Screens::fadeToPal(const Graphics::Palette &ptrpal) {
for (int32 i = 0; i <= 100; i += 3) {
FrameMarker frame(_engine, 66); // VSync()
fadePal(0, 0, 0, ptrpal, i);
}
_engine->setPalette(ptrpal);
_flagBlackPal = false;
}
void Screens::setBlackPal() {
Graphics::Palette workPal(NUMOFCOLORS);
_engine->setPalette(workPal);
_flagBlackPal = true;
}
void Screens::fadePalToPal(const Graphics::Palette &ptrpal, const Graphics::Palette &ptrpal2) {
Graphics::Palette workpal{NUMOFCOLORS};
for (int m = 0; m < 100; ++m) {
FrameMarker frame(_engine, 66); // VSync()
for (int32 i = 0; i < NUMOFCOLORS; i++) {
byte r1, g1, b1;
ptrpal.get(i, r1, g1, b1);
byte r2, g2, b2;
ptrpal2.get(i, r2, g2, b2);
byte newR = ruleThree32(r1, r2, 100, m);
byte newG = ruleThree32(g1, g2, 100, m);
byte newB = ruleThree32(b1, b2, 100, m);
workpal.set(i, newR, newG, newB);
}
_engine->setPalette(workpal);
_engine->_frontVideoBuffer.update();
}
}
void Screens::fadeToRed(const Graphics::Palette &ptrpal) {
for (int32 i = 100; i >= 0; i -= 2) {
FrameMarker frame(_engine, 66); // VSync()
fadePal(255, 0, 0, ptrpal, i);
}
}
void Screens::fadeRedToPal(const Graphics::Palette &ptrpal) {
for (int32 i = 0; i <= 100; i += 2) {
FrameMarker frame(_engine, 66); // VSync()
fadePal(255, 0, 0, ptrpal, i);
}
}
void Screens::copyScreen(const Graphics::ManagedSurface &source, Graphics::ManagedSurface &destination) {
destination.blitFrom(source);
}
void Screens::clearScreen() {
_engine->_frontVideoBuffer.clear(0);
}
} // namespace TwinE

View File

@@ -0,0 +1,151 @@
/* 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 TWINE_RENDERER_SCREENS_H
#define TWINE_RENDERER_SCREENS_H
#include "common/scummsys.h"
#include "graphics/managed_surface.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
#include "twine/twine.h"
namespace TwinE {
class TwinEEngine;
class Screens {
private:
TwinEEngine *_engine;
/**
* Adjust palette intensity
* @param r red component of color
* @param g green component of color
* @param b blue component of color
* @param palette palette to adjust
* @param intensity intensity value to adjust
*/
void fadePal(uint8 r, uint8 g, uint8 b, const Graphics::Palette &palette, int32 intensity);
public:
Screens(TwinEEngine *engine) : _engine(engine) {}
int32 mapLba2Palette(int32 palIndex);
/** main palette */
Graphics::Palette _ptrPal{0};
Graphics::Palette _palettePcx{0};
/** flag to check in the game palette was changed */
bool _flagBlackPal = false;
/** flag to check if the main flag is locked */
bool _flagFade = false;
/** flag to check if we are using a different palette than the main one */
bool _flagPalettePcx = false;
/** Load and display Adeline Logo */
bool adelineLogo();
/**
* Load a custom palette
* @param index \a RESS.HQR entry index (starting from 0)
*/
void loadCustomPalette(const TwineResource &resource);
/** Load and display Main Menu image */
void loadMenuImage(bool fadeIn = true);
/**
* Load and display a particularly image on \a RESS.HQR file with cross fade effect
* @param index \a RESS.HQR entry index (starting from 0)
* @param paletteIndex \a RESS.HQR entry index of the palette for the given image. This is often the @c index + 1
* @param fadeIn if we fade in before using the palette
*/
void loadImage(TwineImage image, bool fadeIn = true);
/**
* Load and display a particularly image on \a RESS.HQR file with cross fade effect and delay
* @param index \a RESS.HQR entry index (starting from 0)
* @param paletteIndex \a RESS.HQR entry index of the palette for the given image. This is often the @c index + 1
* @param seconds number of seconds to delay
* @return @c true if aborted
*/
bool loadImageDelay(TwineImage image, int32 seconds);
bool loadBitmapDelay(const char *image, int32 seconds);
/**
* Adjust between two palettes
* @param pal1 palette from adjust
* @param pal2 palette to adjust
*/
void fadePalToPal(const Graphics::Palette &pal1, const Graphics::Palette &pal2);
/**
* Fade image to black
* @param palette current palette to fade
*/
void fadeToBlack(const Graphics::Palette &palette);
void fadeWhiteToPal(const Graphics::Palette &ptrpal);
/**
* Fade image with another palette source
* @param palette current palette to fade
*/
void fadeToPal(const Graphics::Palette &palette);
/** Fade black palette to white palette */
void whiteFade();
/** Resets both in-game and sdl palettes */
void setBlackPal();
/**
* Fade palette to red palette
* @param palette current palette to fade
*/
void fadeToRed(const Graphics::Palette &palette);
/**
* Fade red to palette
* @param palette current palette to fade
*/
void fadeRedToPal(const Graphics::Palette &palette);
/**
* Copy a determinate screen buffer to another
* @param source screen buffer
* @param destination screen buffer
*/
void copyScreen(const Graphics::ManagedSurface &source, Graphics::ManagedSurface &destination);
/** Clear front buffer screen */
void clearScreen(); // Cls()
/** Init palettes */
void initPalettes();
};
} // namespace TwinE
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,375 @@
/* 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 "twine/resources/hqr.h"
#include "twine/resources/lzss.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/system.h"
#include "common/textconsole.h"
namespace TwinE {
namespace HQR {
#define wrap(cmd) \
if ((cmd) == 0) { \
warning("Failed to execute " #cmd); \
return 0; \
}
/**
* Decompress entry based in Yaz0r and Zink decompression code
* @param dst destination pointer where will be the decompressed entry
* @param compBuf compressed data pointer
* @param compSize @p compBuf buffer size
* @param decompsize real file size after decompression
* @param mode compression mode used
*/
static void decompressEntry(uint8 *dst, const uint8 *compBuf, uint32 compSize, int32 decompsize, int32 mode) {
Common::MemoryReadStream stream(compBuf, compSize);
do {
uint8 b = stream.readByte();
for (int32 d = 0; d < 8; d++) {
int32 length;
if (!(b & (1 << d))) {
const uint16 offset = stream.readUint16LE();
length = (offset & 0x0F) + (mode + 1);
const uint8 *ptr = dst - (offset >> 4) - 1;
for (int32 i = 0; i < length; i++) {
*(dst++) = *(ptr++);
}
} else {
length = 1;
*(dst++) = stream.readByte();
}
decompsize -= length;
if (decompsize <= 0) {
return;
}
}
} while (decompsize);
}
/**
* Get a HQR entry pointer
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
static int voxEntrySize(const char *filename, int32 index, int32 hiddenIndex) {
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
uint32 compSize = file.readUint32LE();
// exist hidden entries
for (int32 i = 0; i < hiddenIndex; i++) {
wrap(file.seek(offsetToData + compSize + 10)) // hidden entry
offsetToData = offsetToData + compSize + 10; // current hidden offset
realSize = file.readUint32LE();
compSize = file.readUint32LE();
}
return realSize;
}
int32 getEntry(uint8 *ptr, const char *filename, int32 index) {
if (!ptr) {
return 0;
}
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
uint32 compSize = file.readUint32LE();
uint16 mode = file.readUint16LE();
// uncompressed
if (mode == 0) {
wrap(file.read(ptr, realSize))
}
// compressed: modes (1 & 2)
else if (mode == 1 || mode == 2) {
uint8 *compDataPtr = (uint8 *)malloc(compSize);
wrap(file.read(compDataPtr, compSize))
decompressEntry(ptr, compDataPtr, compSize, realSize, mode);
free(compDataPtr);
}
debugC(1, TwinE::kDebugResources, "Loaded entry from %s for index %i with %i bytes", filename, index, realSize);
return realSize;
}
int32 entrySize(const char *filename, int32 index) {
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
return realSize;
}
int32 numEntries(const char *filename) {
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
return ((int)headerSize / 4) - 1;
}
Common::SeekableReadStream *makeReadStream(const char *filename, int index) {
Common::File *file = new Common::File();
if (!file->open(filename)) {
delete file;
warning("HQR: Could not open %s", filename);
return nullptr;
}
const uint32 headerSize = file->readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index: %i", index);
delete file;
return nullptr;
}
if (!file->seek(index * 4)) {
warning("HQR: Invalid index: %i", index);
delete file;
return nullptr;
}
const uint32 offsetToData = file->readUint32LE();
if (!file->seek(offsetToData)) {
warning("HQR: Invalid index: %i", index);
delete file;
return nullptr;
}
const uint32 realSize = file->readUint32LE();
const uint32 compressedSize = file->readUint32LE();
const uint16 mode = file->readUint16LE();
const uint32 begin = offsetToData + 10;
uint32 end = 0;
if (mode == 0) {
end = begin + realSize;
} else {
end = begin + compressedSize;
}
Common::SeekableReadStream *stream = new Common::SeekableSubReadStream(file, begin, end, DisposeAfterUse::YES);
if (mode != 0) {
stream = new LzssReadStream(stream, mode, realSize);
}
debugC(1, TwinE::kDebugResources, "Loaded entry from %s for index %i with %i bytes", filename, index, realSize);
return stream;
}
int32 getAllocEntry(uint8 **ptr, const char *filename, int32 index) {
if (*ptr) {
free(*ptr);
}
const int32 size = entrySize(filename, index);
if (size <= 0) {
*ptr = nullptr;
warning("HQR: failed to get entry for index %i from file: %s", index, filename);
return 0;
}
*ptr = (uint8 *)malloc(size * sizeof(uint8));
if (!*ptr) {
warning("HQR: unable to allocate entry memory");
return 0;
}
const int32 entrySize = getEntry(*ptr, filename, index);
assert(entrySize == size);
return entrySize;
}
bool dumpEntry(const char *filename, int32 index, const char *targetFileName) {
Common::DumpFile out;
if (!out.open(targetFileName, true)) {
warning("Failed to save to %s", targetFileName);
return false;
}
uint8 *content = nullptr;
const int size = getAllocEntry(&content, filename, index);
if (size == 0) {
warning("Could not get hqr entry in %s for index %i", filename, index);
return false;
}
out.write(content, size);
out.flush();
out.close();
free(content);
return true;
}
int32 getVoxEntry(uint8 *ptr, const char *filename, int32 index, int32 hiddenIndex) {
if (!ptr) {
return 0;
}
if (!filename) {
return 0;
}
Common::File file;
if (!file.open(filename)) {
warning("HQR: Could not open %s", filename);
return 0;
}
uint32 headerSize = file.readUint32LE();
if ((uint32)index >= headerSize / 4) {
warning("HQR: Invalid entry index");
return 0;
}
wrap(file.seek(index * 4))
uint32 offsetToData = file.readUint32LE();
wrap(file.seek(offsetToData))
uint32 realSize = file.readUint32LE();
uint32 compSize = file.readUint32LE();
uint16 mode = file.readSint16LE();
// exist hidden entries
for (int32 i = 0; i < hiddenIndex; i++) {
wrap(file.seek(offsetToData + compSize + 10)) // hidden entry
offsetToData = offsetToData + compSize + 10; // current hidden offset
realSize = file.readUint32LE();
compSize = file.readUint32LE();
mode = file.readUint16LE();
}
// uncompressed
if (mode == 0) {
wrap(file.read(ptr, realSize))
}
// compressed: modes (1 & 2)
else if (mode == 1 || mode == 2) {
uint8 *compDataPtr = (uint8 *)malloc(compSize);
wrap(file.read(compDataPtr, compSize))
decompressEntry(ptr, compDataPtr, compSize, realSize, mode);
free(compDataPtr);
}
debugC(1, TwinE::kDebugResources, "Loaded vox entry from %s for index %i with %i bytes", filename, index, realSize);
return realSize;
}
bool getPaletteEntry(Graphics::Palette &palette, const char *filename, int32 index) {
byte paletteBuffer[NUMOFCOLORS * 3];
int32 size = HQR::getEntry(paletteBuffer, filename, index);
if (size <= 0) {
debugC(1, TwinE::kDebugResources, "Failed to load palette from %s for index %i", filename, index);
return false;
}
palette = Graphics::Palette(paletteBuffer, size / 3);
debugC(1, TwinE::kDebugResources, "Loaded palette from %s for index %i with %i color entries", filename, index, (int)palette.size());
debugC(1, TwinE::kDebugPalette, "Loaded palette from %s for index %i with %i color entries", filename, index, (int)palette.size());
return true;
}
int32 getAllocVoxEntry(uint8 **ptr, const char *filename, int32 index, int32 hiddenIndex) {
const int32 size = voxEntrySize(filename, index, hiddenIndex);
if (size == 0) {
warning("HQR: vox entry with 0 size found for index: %d", index);
return 0;
}
*ptr = (uint8 *)malloc(size * sizeof(uint8));
if (!*ptr) {
warning("HQR: unable to allocate entry memory of size %d for index: %d", size, index);
return 0;
}
const int32 entrySize = getVoxEntry(*ptr, filename, index, hiddenIndex);
assert(entrySize == size);
return entrySize;
}
#undef wrap
} // namespace HQR
} // namespace TwinE

View File

@@ -0,0 +1,124 @@
/* 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 TWINE_RESOURCES_HQR_H
#define TWINE_RESOURCES_HQR_H
#include "common/scummsys.h"
#include "common/stream.h"
#include "graphics/palette.h"
#include "twine/shared.h"
namespace TwinE {
class TwinEEngine;
/**
* High Quality Resource
*
* https://web.archive.org/web/20181218233826/http://lbafileinfo.kazekr.net/index.php?title=High_quality_resource
*/
namespace HQR {
/**
* Get a HQR entry pointer
* @param ptr pointer to save the entry
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getEntry(uint8 *ptr, const char *filename, int32 index);
inline int32 getEntry(uint8 *ptr, const TwineResource &resource) {
return getEntry(ptr, resource.hqr, resource.index);
}
/**
* Get a HQR entry pointer
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 entrySize(const char *filename, int32 index);
inline int32 entrySize(const TwineResource &resource) {
return entrySize(resource.hqr, resource.index);
}
/**
* Get a HQR total number of entries
* @param filename HQR file name
* @return total number of entries
*/
int32 numEntries(const char *filename);
/**
* Get a HQR entry pointer with memory allocation
* @param ptr pointer to save the entry. This pointer is automatically freed and therefore must be initialized
* to @c nullptr on the first run.
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getAllocEntry(uint8 **ptr, const char *filename, int32 index);
inline int32 getAllocEntry(uint8 **ptr, const TwineResource &resource) {
return getAllocEntry(ptr, resource.hqr, resource.index);
}
/**
* @brief Helper method to dump the content of the given hqr index to a file
*/
bool dumpEntry(const char *filename, int32 index, const char *targetFileName);
inline bool dumpEntry(const TwineResource &resource, const char *targetFileName) {
return dumpEntry(resource.hqr, resource.index, targetFileName);
}
/**
* Get a HQR entry pointer
* @param ptr pointer to save the entry
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getVoxEntry(uint8 *ptr, const char *filename, int32 index, int32 hiddenIndex);
/**
* Get a HQR entry pointer with memory allocation
* @param ptr pointer to save the entry. This pointer is automatically freed and therefore must be initialized
* to @c nullptr on the first run.
* @param filename HQR file name
* @param index entry index to extract
* @return entry real size
*/
int32 getAllocVoxEntry(uint8 **ptr, const char *filename, int32 index, int32 hiddenIndex);
bool getPaletteEntry(Graphics::Palette &palette, const char *filename, int32 index);
inline bool getPaletteEntry(Graphics::Palette &palette, const TwineResource &resource) {
return getPaletteEntry(palette, resource.hqr, resource.index);
}
Common::SeekableReadStream *makeReadStream(const char *filename, int index);
inline Common::SeekableReadStream *makeReadStream(const TwineResource &resource) {
return makeReadStream(resource.hqr, resource.index);
}
} // namespace HQR
} // namespace TwinE
#endif

View 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/>.
*
*/
#include "twine/resources/lzss.h"
#include "common/textconsole.h"
namespace TwinE {
LzssReadStream::LzssReadStream(Common::ReadStream *indata, uint32 mode, uint32 realsize) {
_outLzssBufData = new uint8[realsize]();
decodeLZSS(indata, mode, realsize);
_size = realsize;
_pos = 0;
delete indata;
}
LzssReadStream::~LzssReadStream() {
delete[] _outLzssBufData;
}
void LzssReadStream::decodeLZSS(Common::ReadStream *in, uint32 mode, uint32 dataSize) {
if (in->eos() || in->err() || dataSize == 0) {
_err = dataSize > 0;
return;
}
uint8 *dst = _outLzssBufData;
int32 remainingBytes = (int32)dataSize;
do {
uint8 b = in->readByte();
for (int32 d = 0; d < 8; d++) {
if (in->eos() || in->err()) {
_err = dataSize > 0;
return;
}
int32 length;
if (!(b & (1 << d))) {
const uint16 offset = in->readUint16LE();
length = (offset & 0x0F) + (mode + 1);
const uint8 *ptr = dst - (offset >> 4) - 1;
if (remainingBytes < length) {
_err = true;
return;
}
remainingBytes -= length;
for (int32 i = 0; i < length; i++) {
*dst++ = *ptr++;
}
} else {
length = 1;
if (remainingBytes < length) {
_err = true;
return;
}
remainingBytes -= length;
*dst++ = in->readByte();
}
dataSize -= length;
if (dataSize <= 0) {
return;
}
}
} while (dataSize);
}
bool LzssReadStream::eos() const {
return _pos >= _size;
}
uint32 LzssReadStream::read(void *buf, uint32 dataSize) {
if (dataSize > _size - _pos) {
_err = true;
return 0;
}
memcpy(buf, &_outLzssBufData[_pos], dataSize);
_pos += dataSize;
return dataSize;
}
bool LzssReadStream::seek(int64 offset, int whence) {
if (whence == SEEK_SET) {
_pos = offset;
} else if (whence == SEEK_CUR) {
_pos += offset;
}
return true;
}
} // namespace TwinE

View File

@@ -0,0 +1,54 @@
/* 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 TWINE_RESOURCES_LZSS_H
#define TWINE_RESOURCES_LZSS_H
#include "common/stream.h"
namespace TwinE {
class LzssReadStream : public Common::SeekableReadStream {
private:
uint8 *_outLzssBufData;
uint32 _size;
uint32 _pos;
bool _err = false;
void decodeLZSS(Common::ReadStream *indata, uint32 mode, uint32 length);
public:
LzssReadStream(Common::ReadStream *indata, uint32 mode, uint32 realsize);
virtual ~LzssReadStream();
void clearErr() override { _err = false; }
bool err() const override { return _err; }
int64 pos() const override { return _pos; }
int64 size() const override { return _size; }
bool seek(int64 offset, int whence = SEEK_SET) override;
bool eos() const override;
uint32 read(void *buf, uint32 size) override;
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,325 @@
/* 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 "twine/resources/resources.h"
#include "common/file.h"
#include "common/tokenizer.h"
#include "common/util.h"
#include "graphics/palette.h"
#include "twine/audio/sound.h"
#include "twine/parser/anim3ds.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/scene/animations.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Resources::~Resources() {
for (size_t i = 0; i < ARRAYSIZE(_spriteTable); ++i) {
free(_spriteTable[i]);
}
for (size_t i = 0; i < ARRAYSIZE(_samplesTable); ++i) {
free(_samplesTable[i]);
}
free(_fontPtr);
free(_sjisFontPtr);
}
void Resources::initPalettes() {
if (!HQR::getPaletteEntry(_engine->_screens->_ptrPal, Resources::HQR_RESS_FILE, RESSHQR_MAINPAL)) {
error("Failed to load main palette");
}
_engine->setPalette(_engine->_screens->_ptrPal);
}
void Resources::preloadAnim3DS() {
const int index = HQR::numEntries(Resources::HQR_ANIM3DS_FILE) - 1;
_anim3DSData.loadFromHQR(Resources::HQR_ANIM3DS_FILE, index, _engine->isLBA1());
}
void Resources::loadEntityData(EntityData &entityData, int32 &index) {
if (_engine->isLBA1()) {
TwineResource modelRes(Resources::HQR_FILE3D_FILE, index);
if (!entityData.loadFromHQR(modelRes, _engine->isLBA1())) {
error("Failed to load actor 3d data for index: %i", index);
}
} else {
// TODO: don't allocate each time
TwineResource modelRes(Resources::HQR_RESS_FILE, 44);
uint8 *file3dBuf = nullptr;
const int32 holomapImageSize = HQR::getAllocEntry(&file3dBuf, modelRes);
if (!entityData.loadFromBuffer((uint8 *)(file3dBuf + *(((uint32 *)file3dBuf) + (index))), holomapImageSize, _engine->isLBA1())) {
delete file3dBuf;
error("Failed to load actor 3d data for index: %i", index);
}
delete file3dBuf;
}
}
const T_ANIM_3DS *Resources::getAnim(int index) const {
if (index < 0 || index >= (int)_anim3DSData.getAnims().size()) {
return nullptr;
}
return &_anim3DSData.getAnims()[index];
}
void Resources::preloadSprites() {
const int32 numEntries = HQR::numEntries(Resources::HQR_SPRITES_FILE);
const int32 maxSprites = _engine->isLBA1() ? 200 : NUM_SPRITES;
if (numEntries > maxSprites) {
error("Max allowed sprites exceeded: %i/%i", numEntries, maxSprites);
}
debugC(1, TwinE::kDebugResources, "preload %i sprites", numEntries);
for (int32 i = 0; i < numEntries; i++) {
_spriteSizeTable[i] = HQR::getAllocEntry(&_spriteTable[i], Resources::HQR_SPRITES_FILE, i);
if (!_spriteData[i].loadFromBuffer(_spriteTable[i], _spriteSizeTable[i], _engine->isLBA1())) {
warning("Failed to load sprite %i", i);
}
}
}
void Resources::preloadAnimations() {
const int32 numEntries = HQR::numEntries(Resources::HQR_ANIM_FILE);
const int32 maxAnims = _engine->isLBA1() ? 600 : NUM_ANIMS;
if (numEntries > maxAnims) {
error("Max allowed animations exceeded: %i/%i", numEntries, maxAnims);
}
debugC(1, TwinE::kDebugResources, "preload %i animations", numEntries);
for (int32 i = 0; i < numEntries; i++) {
_animData[i].loadFromHQR(Resources::HQR_ANIM_FILE, i, _engine->isLBA1());
}
}
static bool isLba1BlankSampleEntry(int32 index) {
// these indices contain blank hqr entries
const int32 blankIndices[] = {80, 81, 82, 83, 115, 118, 120, 124, 125, 139, 140, 154, 155};
for (int j = 0; j < ARRAYSIZE(blankIndices); ++j) {
if (index == blankIndices[j]) {
return true;
}
}
return false;
}
void Resources::preloadSamples() {
const int32 numEntries = HQR::numEntries(Resources::HQR_SAMPLES_FILE);
const int32 maxSamples = _engine->isLBA1() ? 243 : NUM_SAMPLES;
if (numEntries > maxSamples) {
error("Max allowed samples exceeded: %i/%i", numEntries, maxSamples);
}
debugC(1, TwinE::kDebugResources, "preload %i samples", numEntries);
for (int32 i = 0; i < numEntries; i++) {
if (_engine->isLBA1() && isLba1BlankSampleEntry(i)) {
_samplesSizeTable[i] = 0;
_samplesTable[i] = nullptr;
continue;
}
_samplesSizeTable[i] = HQR::getAllocEntry(&_samplesTable[i], Resources::HQR_SAMPLES_FILE, i);
if (_samplesSizeTable[i] == 0) {
warning("Failed to load sample %i", i);
continue;
}
// Fix incorrect sample files first byte
if (*_samplesTable[i] != 'C') {
debugC(1, TwinE::kDebugResources, "Sample %i has incorrect magic id (size: %u)", i, _samplesSizeTable[i]);
*_samplesTable[i] = 'C';
}
}
}
void Resources::preloadInventoryItems() {
if (!_engine->isLBA1()) {
// lba2 has this data in code
return;
}
int32 numEntries = HQR::numEntries(Resources::HQR_INVOBJ_FILE);
if (_engine->isPreview()) {
if (numEntries != 32) {
error("Unexpected inventory items for lba1 preview version: %i/32", numEntries);
}
// TODO: this is obviously a hack
numEntries = NUM_INVENTORY_ITEMS;
} else {
if (numEntries > NUM_INVENTORY_ITEMS) {
error("Max allowed inventory items exceeded: %i/%i", numEntries, NUM_INVENTORY_ITEMS);
}
}
debugC(1, TwinE::kDebugResources, "preload %i inventory items", numEntries);
for (int32 i = 0; i < numEntries; i++) {
_inventoryTable[i].loadFromHQR(Resources::HQR_INVOBJ_FILE, i, _engine->isLBA1());
}
}
void Resources::initResources() {
initPalettes();
_fontBufSize = HQR::getAllocEntry(&_fontPtr, Resources::HQR_RESS_FILE, RESSHQR_LBAFONT);
if (_fontBufSize == 0) {
error("Failed to load font");
}
const int kMinSjisSize = 11072 * 24 * 3;
Common::File f24;
if (f24.open("FNT24.DAT") && f24.size() >= kMinSjisSize) {
// Rest is garbage
_sjisFontPtr = (byte *)malloc(kMinSjisSize);
assert(_sjisFontPtr);
f24.read(_sjisFontPtr, kMinSjisSize);
}
_engine->_text->setFont(INTER_LEAVE, INTER_SPACE);
_engine->_text->setFontColor(COLOR_14);
_engine->_text->setTextCrossColor(136, 143, 2);
if (_engine->isLBA1()) {
if (!_spriteShadowPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_SPRITESHADOW), _engine->isLBA1())) {
error("Failed to load shadow sprites");
}
if (!_spriteBoundingBox.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_SPRITEBOXDATA), _engine->isLBA1())) {
error("Failed to load sprite bounding box data");
}
if (!_holomapTwinsenModelPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOTWINMDL), _engine->isLBA1())) {
error("Failed to load holomap twinsen model");
}
if (!_holomapPointModelPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOPOINTMDL), _engine->isLBA1())) {
if (!_engine->isPreview()) {
error("Failed to load holomap point model");
}
}
if (!_holomapArrowPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOARROWMDL), _engine->isLBA1())) {
error("Failed to load holomap arrow model");
}
if (!_holomapTwinsenArrowPtr.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOTWINARROWMDL), _engine->isLBA1())) {
error("Failed to load holomap twinsen arrow model");
}
if (!_trajectories.loadFromHQR(TwineResource(Resources::HQR_RESS_FILE, RESSHQR_HOLOPOINTANIM), _engine->isLBA1())) {
if (!_engine->isPreview()) {
error("Failed to parse trajectory data");
}
}
debugC(1, TwinE::kDebugResources, "preload %i trajectories", (int)_trajectories.getTrajectories().size());
} else if (_engine->isLBA2()) {
preloadAnim3DS();
}
preloadSprites();
preloadAnimations();
preloadSamples();
preloadInventoryItems();
loadMovieInfo();
if (!_engine->isPreview()) {
// TODO: where is the text in the preview version?
const int32 textEntryCount = _engine->isLBA1() ? 28 : 30;
for (int32 i = 0; i < textEntryCount / 2; ++i) {
if (!_textData.loadFromHQR(Resources::HQR_TEXT_FILE, (TextBankId)i, _engine->_cfgfile._languageId, _engine->isLBA1(), textEntryCount)) {
error("HQR ERROR: Parsing textbank %i failed for language %i (%i entries)", i, _engine->_cfgfile._languageId, textEntryCount);
}
}
debugC(1, TwinE::kDebugResources, "Loaded %i text banks", textEntryCount / 2);
}
}
const TextEntry *Resources::getText(TextBankId textBankId, TextId index) const {
return _textData.getText(textBankId, index);
}
const Trajectory *Resources::giveTrajPtr(int index) const {
return _trajectories.getTrajectory(index);
}
int Resources::findSmkMovieIndex(const char *name) const {
Common::String smkName = name;
smkName.toLowercase();
if (!_movieInfo.contains(smkName)) {
warning("Movie '%s' not found in movie info", smkName.c_str());
return -1;
}
const Common::Array<int32> &info = getMovieInfo(smkName);
return info[0];
}
void Resources::loadMovieInfo() {
uint8 *content = nullptr;
int32 size;
if (_engine->isLBA1()) {
if (_engine->isPreview()) {
size = 0;
} else {
size = HQR::getAllocEntry(&content, Resources::HQR_RESS_FILE, RESSHQR_FLAINFO);
}
} else {
size = HQR::getAllocEntry(&content, Resources::HQR_RESS_FILE, 48);
}
if (size == 0) {
return;
}
const Common::String str((const char *)content, size);
free(content);
debugC(2, TwinE::kDebugResources, "movie info:\n%s", str.c_str());
Common::StringTokenizer tok(str, "\r\n");
int videoIndex = 0;
while (!tok.empty()) {
Common::String line = tok.nextToken();
if (_engine->isLBA1()) {
Common::StringTokenizer lineTok(line);
if (lineTok.empty()) {
continue;
}
const Common::String &name = lineTok.nextToken();
Common::Array<int32> frames;
while (!lineTok.empty()) {
const Common::String &frame = lineTok.nextToken();
const int32 frameIdx = atoi(frame.c_str());
frames.push_back(frameIdx);
}
_movieInfo.setVal(name, frames);
} else {
Common::Array<int32> info(1);
info[0] = videoIndex;
line.toLowercase();
if (line.hasSuffix(".smk")) {
line = line.substr(0, line.size() - 4);
}
_movieInfo.setVal(line, info);
debugC(1, TwinE::kDebugResources, "movie name %s mapped to hqr index %i", line.c_str(), videoIndex);
++videoIndex;
}
}
}
const Common::Array<int32> &Resources::getMovieInfo(const Common::String &name) const {
return _movieInfo.getVal(name);
}
} // namespace TwinE

View File

@@ -0,0 +1,304 @@
/* 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 TWINE_RESOURCES_RESOURCES_H
#define TWINE_RESOURCES_RESOURCES_H
#include "common/hashmap.h"
#include "common/scummsys.h"
#include "twine/parser/anim3ds.h"
#include "twine/parser/body.h"
#include "twine/parser/holomap.h"
#include "twine/parser/sprite.h"
#include "twine/parser/text.h"
#include "twine/resources/hqr.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/scene.h"
namespace TwinE {
/** RESS.HQR FILE */
#define RESSHQR_MAINPAL 0
#define RESSHQR_LBAFONT 1
#define RESSHQR_BLANK 2
#define RESSHQR_SPRITEBOXDATA 3
#define RESSHQR_SPRITESHADOW 4
#define RESSHQR_HOLOPAL 5 // lba1
#define RESSHQR_HOLOSURFACE 6 // lba1
#define RESSHQR_HOLOIMG 7 // lba1
#define RESSHQR_HOLOARROWINFO 8 // lba1
#define RESSHQR_HOLOTWINMDL 9 // lba1
#define RESSHQR_HOLOARROWMDL 10 // lba1
#define RESSHQR_HOLOTWINARROWMDL 11 // lba1
#define RESSHQR_BLACKPAL 9 // lba2
#define RESSHQR_ECLAIRPAL 10 // lba2
#define RESSHQR_ARROWBIN 12 // lba2
#define SAMPLE_RAIN 13
#define RESSHQR_GAMEOVERMDL 21
#define RESSHQR_ALARMREDPAL 22
#define RESSHQR_FLAINFO 23
#define RESSHQR_DARKPAL 24
#define RESSHQR_HOLOPOINTMDL 29
#define RESSHQR_HOLOPOINTANIM 30
#define RESSHQR_PLASMAEFFECT 51
#define FLA_DRAGON3 "dragon3"
#define FLA_INTROD "introd"
#define FLA_THEEND "the_end"
#define FLA_BATEAU "bateau"
#define ACF_INTRO "INTRO"
#define FILE3DHQR_HERONORMAL 0
#define FILE3DHQR_HEROATHLETIC 1
#define FILE3DHQR_HEROAGGRESSIVE 2
#define FILE3DHQR_HERODISCRETE 3
#define FILE3DHQR_HEROPROTOPACK 4
/** Behaviour menu sprite values */
#define SPRITEHQR_KASHES 3
#define SPRITEHQR_LIFEPOINTS 4
#define SPRITEHQR_MAGICPOINTS 5
#define SPRITEHQR_KEY 6
#define SPRITEHQR_CLOVERLEAF 7
#define SPRITEHQR_CLOVERLEAFBOX 41
#define SPRITEHQR_MAGICBALL_YELLOW 1
#define SPRITEHQR_MAGICBALL_FIRE 13
#define SPRITEHQR_MAGICBALL_GREEN 42
#define SPRITEHQR_MAGICBALL_RED 43
#define SPRITEHQR_MAGICBALL_YELLOW_TRANS 44
#define SPRITEHQR_EXPLOSION_FIRST_FRAME 97 // 7 frames
#define SPRITEHQR_FENCE_1 18
#define SPRITEHQR_FENCE_2 19
#define SPRITEHQR_FENCE_3 22
#define SPRITEHQR_FENCE_4 23
#define SPRITEHQR_FENCE_METAL 35
#define SPRITEHQR_FENCE_METAL_2 54
#define SPRITEHQR_FENCE_METAL_3 83
#define SPRITEHQR_MUSHROOM 92
#define SPRITEHQR_DOOR_WODDEN_1 31
#define SPRITEHQR_DOOR_WODDEN_2 32
#define SPRITEHQR_DOOR_PRISON_WODDEN 37
#define SPRITEHQR_DOOR_PADLOCK 58
#define SPRITEHQR_DOOR_BRICKED_UP 76
#define SPRITEHQR_DOOR_1 104
#define SPRITEHQR_DOOR_2 107
#define SPRITEHQR_DOOR_3 24
#define SPRITEHQR_DOOR_4 11
#define SPRITEHQR_DOOR_5 12
#define SPRITEHQR_DOOR_PRISON_GRID 15
#define SPRITEHQR_DOOR_PRISON_HARMED 16
#define SPRITEHQR_DOOR_PRISON_WITH_F_LETTER 17
#define SPRITEHQR_MAGICBALL_GREEN_TRANS 109
#define SPRITEHQR_MAGICBALL_RED_TRANS 110
#define SPRITEHQR_DIAG_BUBBLE_RIGHT 90
#define SPRITEHQR_DIAG_BUBBLE_LEFT 91
/** Total number of animations allowed in the game */
#define NUM_ANIMS 2083 // 600 for lba1
/** Total number of samples allowed in the game */
#define NUM_SAMPLES 895 // 243 for lba1
class TwinEEngine;
class Resources {
private:
TwinEEngine *_engine;
void preloadInventoryItems();
/** Init standard menu and in-game palette */
void initPalettes();
/** Preload all sprites */
void preloadSprites();
/** Preload all animations */
void preloadAnimations();
void preloadAnim3DS();
void preloadSamples();
void loadMovieInfo();
using MovieInfoMap = Common::HashMap<Common::String, Common::Array<int32> >;
MovieInfoMap _movieInfo;
TrajectoryData _trajectories;
TextData _textData;
Anim3DSData _anim3DSData;
public:
Resources(TwinEEngine *engine) : _engine(engine) {}
~Resources();
/**
* For lba1 this is returning the gif images that are used as a placeholder for the fla movies
* For lba2 this is the list of videos that are mapped by their entry index
*/
const Common::Array<int32> &getMovieInfo(const Common::String &name) const;
/** Table with all loaded samples */
BodyData _inventoryTable[NUM_INVENTORY_ITEMS];
/** Table with all loaded sprites */
uint8 *_spriteTable[NUM_SPRITES]{nullptr};
/** Table with all loaded sprite sizes */
uint32 _spriteSizeTable[NUM_SPRITES]{0};
SpriteData _spriteData[NUM_SPRITES];
AnimData _animData[NUM_ANIMS]; // HQR_Anims
/** Table with all loaded samples */
uint8 *_samplesTable[NUM_SAMPLES]{nullptr};
/** Table with all loaded samples sizes */
uint32 _samplesSizeTable[NUM_SAMPLES]{0};
/** Font buffer pointer */
int32 _fontBufSize = 0;
uint8 *_fontPtr = nullptr;
uint8 *_sjisFontPtr = nullptr;
SpriteData _spriteShadowPtr;
SpriteBoundingBoxData _spriteBoundingBox;
BodyData _holomapPointModelPtr;
BodyData _holomapTwinsenModelPtr;
BodyData _holomapTwinsenArrowPtr;
BodyData _holomapArrowPtr;
/** Initialize resource pointers */
void initResources();
const Trajectory *giveTrajPtr(int index) const;
const TrajectoryData &getTrajectories() const {
return _trajectories;
}
void loadEntityData(EntityData &entityData, int32 &index);
const TextEntry *getText(TextBankId textBankId, TextId index) const;
const T_ANIM_3DS *getAnim(int index) const;
int findSmkMovieIndex(const char *name) const;
// main palette
static constexpr const char *HQR_RESS_FILE = "ress.hqr";
// dialoges
static constexpr const char *HQR_TEXT_FILE = "text.hqr";
// samples
static constexpr const char *HQR_SAMPLES_FILE = "samples.hqr";
/**
* This file contains isometric grids that are used to display area backgrounds and define 3D shape of the surface.
* Each of the entries is associated with the entry of lba_bll.hqr with the same index. lba_bll entries define block
* sets for use with the grids. Each grid may use only one set of blocks (one entry of lba_bll.hqr).
*/
static constexpr const char *HQR_LBA_GRI_FILE = "lba_gri.hqr";
// isometric libraries for use in grids.
static constexpr const char *HQR_LBA_BLL_FILE = "lba_bll.hqr";
/**
* isometric bricks, which are some kind of tiles, that are used for building the terrains in LBA 1 isometric scenes.
* One brick is the tiniest piece of a grid, which has 64 x 64 x 25 cells. Bricks cannot be used directly on a grid,
* but instead they are grouped into blocks by block libraries, which are then referenced by grids
* Bricks are images or sprites in a special format.
*/
static constexpr const char *HQR_LBA_BRK_FILE = "lba_brk.hqr";
// scenes (active area content (actors, scripts, etc.))
static constexpr const char *HQR_SCENE_FILE = "scene.hqr";
// full screen images (lba2)
static constexpr const char *HQR_SCREEN_FILE = "screen.hqr";
// sprites
static constexpr const char *HQR_SPRITES_FILE = "sprites.hqr";
/**
* model/animation entities
* contains data associating 3D models (Body.hqr) with animations (Anim.hqr) for the game characters.
*/
static constexpr const char *HQR_FILE3D_FILE = "file3d.hqr";
// 3d model data
static constexpr const char *HQR_BODY_FILE = "body.hqr";
// animations
static constexpr const char *HQR_ANIM_FILE = "anim.hqr";
static constexpr const char *HQR_ANIM3DS_FILE = "anim3ds.hqr";
// inventory objects
static constexpr const char *HQR_INVOBJ_FILE = "invobj.hqr";
// lba2 holomap
static constexpr const char *HQR_HOLOMAP_FILE = "holomap.hqr";
/**
* @brief Floppy version of the game uses gifs for replacing the videos
*/
static constexpr const char *HQR_FLAGIF_FILE = "fla_gif.hqr";
static constexpr const char *HQR_FLASAMP_FILE = "flasamp.hqr";
static constexpr const char *HQR_MIDI_MI_DOS_FILE = "midi_mi.hqr";
static constexpr const char *HQR_MIDI_MI_WIN_FILE = "midi_mi_win.hqr";
static constexpr const char *HQR_VIDEO_FILE = "video.hqr"; // lba2 - smk files
TwineImage adelineLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 27, 28);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 0, 1);
}
TwineImage lbaLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 49, 50);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 60, 61);
}
TwineImage eaLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 52, 53);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 74, 75);
}
TwineImage activisionLogo() const {
assert(_engine->isLBA2());
return TwineImage(Resources::HQR_SCREEN_FILE, 72, 73);
}
TwineImage virginLogo() const {
assert(_engine->isLBA2());
return TwineImage(Resources::HQR_SCREEN_FILE, 76, 77);
}
TwineImage relentLogo() const {
assert(_engine->isLBA1());
return TwineImage(Resources::HQR_RESS_FILE, 12, 13);
}
TwineImage menuBackground() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 14, -1);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 4, 5);
}
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,550 @@
/* 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 "twine/scene/actor.h"
#include "common/textconsole.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_state.h"
#include "twine/parser/entity.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/scene/animations.h"
#include "twine/scene/extra.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
namespace TwinE {
Actor::Actor(TwinEEngine *engine) : _engine(engine) {
}
void Actor::restartPerso() {
ActorStruct *sceneHero = _engine->_scene->_sceneHero;
sceneHero->_move = ControlMode::kManual;
memset(&sceneHero->_workFlags, 0, sizeof(sceneHero->_workFlags));
memset(&sceneHero->_flags, 0, sizeof(sceneHero->_flags));
sceneHero->_flags.bComputeCollisionWithObj = 1;
sceneHero->_flags.bComputeCollisionWithBricks = 1;
sceneHero->_flags.bCheckZone = 1;
sceneHero->_flags.bCanDrown = 1;
sceneHero->_flags.bObjFallable = 1;
sceneHero->_armor = 1;
sceneHero->_offsetTrack = -1;
sceneHero->_labelTrack = -1;
sceneHero->_offsetLife = 0;
sceneHero->_zoneSce = -1;
sceneHero->_beta = _previousHeroAngle;
_engine->_movements->initRealAngle(sceneHero->_beta, sceneHero->_beta, LBAAngles::ANGLE_0, &sceneHero->realAngle);
setBehaviour(_previousHeroBehaviour);
_cropBottomScreen = 0;
}
void Actor::loadBehaviourEntity(ActorStruct *actor, EntityData &entityData, int16 &bodyAnimIndex, int32 index) {
_engine->_resources->loadEntityData(entityData, index);
actor->_entityDataPtr = &entityData;
bodyAnimIndex = entityData.getAnimIndex(AnimationTypes::kStanding);
if (bodyAnimIndex == -1) {
error("Could not find animation data for 3d data with index %i", index);
}
}
void Actor::loadHeroEntities() {
ActorStruct *sceneHero = _engine->_scene->_sceneHero;
loadBehaviourEntity(sceneHero, _heroEntityATHLETIC, _heroAnimIdxATHLETIC, FILE3DHQR_HEROATHLETIC);
loadBehaviourEntity(sceneHero, _heroEntityAGGRESSIVE, _heroAnimIdxAGGRESSIVE, FILE3DHQR_HEROAGGRESSIVE);
loadBehaviourEntity(sceneHero, _heroEntityDISCRETE, _heroAnimIdxDISCRETE, FILE3DHQR_HERODISCRETE);
loadBehaviourEntity(sceneHero, _heroEntityPROTOPACK, _heroAnimIdxPROTOPACK, FILE3DHQR_HEROPROTOPACK);
loadBehaviourEntity(sceneHero, _heroEntityNORMAL, _heroAnimIdxNORMAL, FILE3DHQR_HERONORMAL);
_engine->_animations->_currentActorAnimExtraPtr = AnimationTypes::kStanding;
sceneHero->_ptrAnimAction = _engine->_animations->_currentActorAnimExtraPtr;
}
void Actor::setBehaviour(HeroBehaviourType behaviour) {
ActorStruct *sceneHero = _engine->_scene->_sceneHero;
switch (behaviour) {
case HeroBehaviourType::kNormal:
_heroBehaviour = behaviour;
sceneHero->_entityDataPtr = &_heroEntityNORMAL;
break;
case HeroBehaviourType::kAthletic:
_heroBehaviour = behaviour;
sceneHero->_entityDataPtr = &_heroEntityATHLETIC;
break;
case HeroBehaviourType::kAggressive:
_heroBehaviour = behaviour;
sceneHero->_entityDataPtr = &_heroEntityAGGRESSIVE;
break;
case HeroBehaviourType::kDiscrete:
_heroBehaviour = behaviour;
sceneHero->_entityDataPtr = &_heroEntityDISCRETE;
break;
case HeroBehaviourType::kProtoPack:
_heroBehaviour = behaviour;
sceneHero->_entityDataPtr = &_heroEntityPROTOPACK;
break;
case HeroBehaviourType::kMax:
break;
}
const BodyType bodyIdx = sceneHero->_genBody;
sceneHero->_body = -1;
sceneHero->_genBody = BodyType::btNone;
initBody(bodyIdx, OWN_ACTOR_SCENE_INDEX);
sceneHero->_genAnim = AnimationTypes::kAnimNone;
sceneHero->_flagAnim = AnimType::kAnimationTypeRepeat;
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, OWN_ACTOR_SCENE_INDEX);
}
void Actor::setFrame(int32 actorIdx, uint32 frame) {
#if 0
// TODO: converted from asm - not yet adapted
ActorStruct *obj = _engine->_scene->getActor(actorIdx);
T_PTR_NUM tempNextBody = obj->NextBody;
void *tempNextTexture = obj->NextTexture;
if (frame >= obj->NbFrames) {
return;
}
obj->_body = tempNextBody;
obj->Texture = tempNextTexture;
T_PTR_NUM tempAnim = obj->_anim;
void (*TransFctAnim)() = nullptr; // Couldn't find this yet
if (TransFctAnim != nullptr) {
uint32 ebp = frame;
TransFctAnim();
tempAnim = (T_PTR_NUM)(void *)tempAnim.Ptr;
frame = ebp;
}
obj->Interpolator = 0;
obj->LastAnimStepX = 0;
obj->LastAnimStepY = 0;
obj->LastAnimStepZ = 0;
uint16 nbGroups = *((uint16 *)(tempAnim.Ptr + 2));
obj->LastAnimStepAlpha = 0;
obj->LastAnimStepBeta = 0;
obj->LastAnimStepGamma = 0;
obj->LastOfsIsPtr = 0;
uint32 lastOfsFrame = nbGroups * 8 + 8; // infos frame + 4 WORDs per group
obj->LastNbGroups = nbGroups;
obj->NextNbGroups = nbGroups;
obj->NbGroups = nbGroups;
lastOfsFrame *= frame;
uint32 timerRefHR = 0; // Replace with actual TimerRefHR
lastOfsFrame += 8; // Skip header
obj->LastTimer = timerRefHR;
obj->Time = timerRefHR;
obj->Status = 1; // STATUS_FRAME
obj->LastOfsFrame = lastOfsFrame;
obj->LastFrame = frame;
uint32 ecx = nbGroups * 2 - 2; // 2 DWORDs per group, no group 0
T_PTR_NUM ebpPtr = tempAnim;
tempAnim.Ptr = tempAnim.Ptr + lastOfsFrame + 16;
memcpy(obj->CurrentFrame, tempAnim.Ptr, ecx);
if (++frame == obj->NbFrames) {
uint16 time = *((uint16 *)(ebpPtr.Ptr + 8));
frame = 0;
tempAnim.Ptr = (void *)(8);
} else {
uint16 time = *((uint16 *)tempAnim.Ptr);
tempAnim.Ptr -= ebpPtr.Ptr;
tempAnim.Num += obj->LastTimer;
obj->NextFrame = frame;
obj->NextOfsFrame = (uint32)(tempAnim.Ptr);
obj->NextTimer = time;
obj->Master = *((uint16 *)(tempAnim.Ptr + 8));
obj->Status = 1; // STATUS_FRAME
}
#endif
}
void Actor::initSprite(int32 spriteNum, int32 actorIdx) {
ActorStruct *localActor = _engine->_scene->getActor(actorIdx);
localActor->_sprite = spriteNum; // lba2
if (!localActor->_flags.bSprite3D) {
return;
}
if (spriteNum != -1 && localActor->_body != spriteNum) {
const BoundingBox *spritebbox = _engine->_resources->_spriteBoundingBox.bbox(spriteNum);
localActor->_body = spriteNum;
localActor->_boundingBox = *spritebbox;
}
}
TextId Actor::getTextIdForBehaviour() const {
if (_heroBehaviour == HeroBehaviourType::kAggressive && _combatAuto) {
return TextId::kBehaviourAggressiveAuto;
}
// the other values are matching the text ids
return (TextId)(int32)_heroBehaviour;
}
int32 Actor::searchBody(BodyType bodyIdx, int32 actorIdx, ActorBoundingBox &actorBoundingBox) {
if (bodyIdx == BodyType::btNone) {
return -1;
}
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const EntityBody *body = actor->_entityDataPtr->getEntityBody((int)bodyIdx);
if (body == nullptr) {
warning("Failed to get entity body for body idx %i", (int)bodyIdx);
return -1;
}
actorBoundingBox = body->actorBoundingBox;
return (int)bodyIdx;
}
void Actor::initBody(BodyType gennewbody, int16 actorIdx) {
ActorStruct *localActor = _engine->_scene->getActor(actorIdx);
if (localActor->_flags.bSprite3D) {
return;
}
debug(1, "Load body %i for actor %i", (int)gennewbody, actorIdx);
if (IS_HERO(actorIdx) && _heroBehaviour == HeroBehaviourType::kProtoPack && gennewbody != BodyType::btTunic && gennewbody != BodyType::btNormal) {
setBehaviour(HeroBehaviourType::kNormal);
}
ActorBoundingBox actorBoundingBox;
const int32 newBody = searchBody(gennewbody, actorIdx, actorBoundingBox);
if (newBody == -1) {
localActor->_genBody = BodyType::btNone;
localActor->_body = -1;
localActor->_boundingBox = BoundingBox();
debug("Failed to initialize body %i for actor %i", (int)gennewbody, actorIdx);
return;
}
if (localActor->_body == newBody) {
return;
}
const int32 oldBody = localActor->_body;
localActor->_body = newBody;
localActor->_genBody = gennewbody;
if (actorBoundingBox.hasBoundingBox) {
localActor->_boundingBox = actorBoundingBox.bbox;
} else {
const BodyData &bd = localActor->_entityDataPtr->getBody(localActor->_body);
localActor->_boundingBox = bd.bbox;
int32 size = 0;
const int32 distX = bd.bbox.maxs.x - bd.bbox.mins.x;
const int32 distZ = bd.bbox.maxs.z - bd.bbox.mins.z;
if (localActor->_flags.bUseMiniZv) {
// take smaller for bound
if (distX < distZ)
size = distX / 2;
else
size = distZ / 2;
} else {
// take average for bound
size = (distZ + distX) / 4;
}
localActor->_boundingBox.mins.x = -size;
localActor->_boundingBox.maxs.x = size;
localActor->_boundingBox.mins.z = -size;
localActor->_boundingBox.maxs.z = size;
}
if (oldBody != -1 && localActor->_anim != -1) {
copyInterAnim(localActor->_entityDataPtr->getBody(oldBody), localActor->_entityDataPtr->getBody(localActor->_body));
}
}
void Actor::copyInterAnim(const BodyData &src, BodyData &dest) {
if (!src.isAnimated() || !dest.isAnimated()) {
return;
}
dest._animTimerData = src._animTimerData;
const int16 numBones = MIN<int16>((int16)src.getNumBones(), (int16)dest.getNumBones());
for (int16 i = 0; i < numBones; ++i) {
const BoneFrame *srcBoneFrame = src.getBoneState(i);
BoneFrame *destBoneFrame = dest.getBoneState(i);
*destBoneFrame = *srcBoneFrame;
}
}
void Actor::startInitObj(int16 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_flags.bSprite3D) {
if (actor->_hitForce != 0) {
actor->_workFlags.bIsHitting = 1;
}
actor->_body = -1;
initSprite(actor->_sprite, actorIdx);
_engine->_movements->initRealAngle(LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, &actor->realAngle);
if (actor->_flags.bSpriteClip) {
actor->_animStep = actor->posObj();
}
} else {
actor->_body = -1;
debug(1, "Init actor %i with model %i", actorIdx, (int)actor->_genBody);
initBody(actor->_genBody, actorIdx);
actor->_anim = -1;
actor->_flagAnim = AnimType::kAnimationTypeRepeat;
if (actor->_body != -1) {
_engine->_animations->initAnim(actor->_genAnim, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
_engine->_movements->initRealAngle(actor->_beta, actor->_beta, LBAAngles::ANGLE_0, &actor->realAngle);
}
actor->_offsetTrack = -1;
actor->_labelTrack = -1;
actor->_offsetLife = 0;
}
void Actor::initObject(int16 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
*actor = ActorStruct(_engine->getMaxLife());
actor->_actorIdx = actorIdx;
actor->_posObj = IVec3(0, SIZE_BRICK_Y, 0);
memset(&actor->_flags, 0, sizeof(StaticFlagsStruct));
memset(&actor->_workFlags, 0, sizeof(DynamicFlagsStruct));
memset(&actor->_bonusParameter, 0, sizeof(BonusParameter));
_engine->_movements->initRealAngle(LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, &actor->realAngle);
}
void Actor::hitObj(int32 actorIdx, int32 actorIdxAttacked, int32 hitforce, int32 angle) {
ActorStruct *actor = _engine->_scene->getActor(actorIdxAttacked);
if (actor->_lifePoint <= 0) {
return;
}
if (IS_HERO(actorIdxAttacked) && _engine->_debugState->_godMode) {
return;
}
actor->_hitBy = actorIdx;
debugC(1, TwinE::kDebugCollision, "Actor %d was hit by %d", actorIdxAttacked, actorIdx);
if (actor->_armor <= hitforce) {
if (actor->_genAnim == AnimationTypes::kBigHit || actor->_genAnim == AnimationTypes::kHit2) {
if (actor->_nextGenAnim != AnimationTypes::kStanding) {
const int32 tmpAnimPos = actor->_frame;
actor->_frame = 1;
_engine->_animations->processAnimActions(actorIdxAttacked);
actor->_frame = tmpAnimPos;
}
} else {
if (angle != -1) {
_engine->_movements->initRealAngle(angle, angle, LBAAngles::ANGLE_0, &actor->realAngle);
}
if (_engine->getRandomNumber() & 1) {
_engine->_animations->initAnim(AnimationTypes::kHit2, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked);
} else {
_engine->_animations->initAnim(AnimationTypes::kBigHit, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked);
}
}
_engine->_extra->initSpecial(actor->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.z, ExtraSpecialType::kHitStars);
if (IS_HERO(actorIdxAttacked)) {
_engine->_movements->_lastJoyFlag = true;
}
// TODO: in the original sources this in an else block - dotemu release doesn't have this (so we are going after dotmeu here)
// else {
actor->_lifePoint -= hitforce;
// }
if (actor->_lifePoint < 0) {
actor->_lifePoint = 0;
}
} else {
_engine->_animations->initAnim(AnimationTypes::kHit, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked);
}
}
void Actor::checkCarrier(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (!actor->_flags.bIsCarrierActor) {
return;
}
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
ActorStruct *otherActor = _engine->_scene->getActor(a);
if (otherActor->_carryBy == actorIdx) {
otherActor->_carryBy = -1;
}
}
}
void Actor::giveExtraBonus(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const int bonusSprite = _engine->_extra->getBonusSprite(actor->_bonusParameter);
if (bonusSprite == -1) {
return;
}
if (actor->_workFlags.bIsDead) {
_engine->_extra->addExtraBonus(actor->posObj(), LBAAngles::ANGLE_90, LBAAngles::ANGLE_0, bonusSprite, actor->_bonusAmount);
_engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, actor->posObj(), actorIdx);
} else {
const ActorStruct *sceneHero = _engine->_scene->_sceneHero;
const int32 angle = _engine->_movements->getAngle(actor->posObj(), sceneHero->posObj());
const IVec3 pos(actor->_posObj.x, actor->_posObj.y + actor->_boundingBox.maxs.y, actor->_posObj.z);
_engine->_extra->addExtraBonus(pos, LBAAngles::ANGLE_70, angle, bonusSprite, actor->_bonusAmount);
_engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, pos, actorIdx);
}
}
// Lba2
#define START_AROUND_BETA 1024
#define END_AROUND_BETA 3072
#define STEP_AROUND_BETA 128 // 16 pos testees
#define GetAngle2D(x0, z0, x1, z1) GetAngleVector2D((x1) - (x0), (z1) - (z0))
void Actor::posObjectAroundAnother(uint8 numsrc, uint8 numtopos) {
#if 0
ActorStruct *objsrc;
ActorStruct *objtopos;
int32 beta, dist, dist2;
int32 step;
objsrc = _engine->_scene->getActor(numsrc);
objtopos = _engine->_scene->getActor(numtopos);
int32 xb = objsrc->Obj.X;
int32 zb = objsrc->Obj.Z;
objtopos->Obj.Y = objsrc->Obj.Y;
dist = MAX(objsrc->XMin, objsrc->XMax);
dist = MAX(dist, objsrc->ZMin);
dist = MAX(dist, objsrc->ZMax);
dist2 = MAX(objtopos->XMin, objtopos->XMax);
dist2 = MAX(dist2, objtopos->ZMin);
dist2 = MAX(dist2, objtopos->ZMax);
dist += dist / 2 + dist2 + dist2 / 2;
beta = ClampAngle(objsrc->Obj.Beta + START_AROUND_BETA);
for (step = 0; step < (4096 / STEP_AROUND_BETA); step++, beta += STEP_AROUND_BETA) {
beta &= 4095;
_engine->_renderer->rotate(0, dist, beta);
objtopos->Obj.X = xb + X0;
objtopos->Obj.Z = zb + Z0;
if (_engine->_collision->checkValidObjPos(numtopos, numsrc)) {
// accepte position
break;
}
}
objtopos->Obj.Beta = ClampAngle(GetAngle2D(xb, zb, objtopos->Obj.X, objtopos->Obj.Z));
#endif
}
int16 RealValue::getRealValueFromTime(int32 time) {
if (timeValue) {
const int32 delta = time - memoTicks;
if (delta >= timeValue) { // rotation is finished
timeValue = 0;
return endValue;
}
int32 t = ((endValue - startValue) * delta) / timeValue;
t += startValue;
return (int16)t;
}
return endValue;
}
int16 RealValue::getRealAngle(int32 time) {
if (timeValue) {
int32 delta = time - memoTicks;
if (delta < timeValue) {
int32 t = NormalizeAngle(endValue - startValue);
t = (t * delta) / timeValue;
t += startValue;
return (int16)t;
}
timeValue = 0;
}
return endValue;
}
bool ActorStruct::isAttackAnimationActive() const {
return _genAnim == AnimationTypes::kRightPunch || _genAnim == AnimationTypes::kLeftPunch || _genAnim == AnimationTypes::kKick;
}
bool ActorStruct::isAttackWeaponAnimationActive() const {
return _genAnim == AnimationTypes::kSabreAttack || _genAnim == AnimationTypes::kThrowBall || _genAnim == AnimationTypes::kSabreUnknown;
}
bool ActorStruct::isJumpAnimationActive() const {
return _genAnim == AnimationTypes::kJump;
}
} // namespace TwinE

378
engines/twine/scene/actor.h Normal file
View File

@@ -0,0 +1,378 @@
/* 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 TWINE_SCENE_ACTOR_H
#define TWINE_SCENE_ACTOR_H
#include "common/scummsys.h"
#include "twine/parser/anim.h"
#include "twine/parser/body.h"
#include "twine/parser/entity.h"
#include "twine/shared.h"
namespace TwinE {
/** Total number of sprites allowed in the game */
#define NUM_SPRITES 425 // 200 for lba1
/** Total number of bodies allowed in the game */
#define NUM_BODIES 469 // 131 for lba1
/** Actors move structure */
struct RealValue {
int16 startValue = 0;
int16 endValue = 0;
int16 timeValue = 0;
int32 memoTicks = 0;
/**
* Get actor real angle
* @param time engine time used for interpolation
*/
int16 getRealValueFromTime(int32 time);
int16 getRealAngle(int32 time);
};
/** Actors static flags structure */
struct StaticFlagsStruct {
uint32 bComputeCollisionWithObj : 1; // 0x000001 CHECK_OBJ_COL
uint32 bComputeCollisionWithBricks : 1; // 0x000002 CHECK_BRICK_COL
uint32 bCheckZone : 1; // 0x000004 CHECK_ZONE - testing of scenaric areas
uint32 bSpriteClip : 1; // 0x000008 SPRITE_CLIP - (doors) fixed clip area
uint32 bCanBePushed : 1; // 0x000010 PUSHABLE
uint32 bComputeLowCollision : 1; // 0x000020 COL_BASSE
uint32 bCanDrown : 1; // 0x000040 CHECK_CODE_JEU
uint32 bComputeCollisionWithFloor : 1; // 0x000080 CHECK_WATER_COL
uint32 bUnk0100 : 1; // 0x000100
uint32 bIsInvisible : 1; // 0x000200 INVISIBLE - not drawn but all computed
uint32 bSprite3D : 1; // 0x000400 SPRITE_3D - a sprite not a 3D object
uint32 bObjFallable : 1; // 0x000800 OBJ_FALLABLE
uint32 bNoShadow : 1; // 0x001000 NO_SHADOW - no auto shadow
uint32 bIsBackgrounded : 1; // 0x002000 OBJ_BACKGROUND - is embedded in the decor the 1st time
uint32 bIsCarrierActor : 1; // 0x004000 OBJ_CARRIER - can carry and move an obj
// take smaller value for bound, or if not set take average for bound
uint32 bUseMiniZv : 1; // 0x008000 MINI_ZV - square on smaller dimension (if 3D object)
uint32 bHasInvalidPosition : 1; // 0x010000 POS_INVALIDE - carrier considered as an invalid position
uint32 bNoElectricShock : 1; // 0x020000 NO_CHOC - does not trigger electric shock animation
uint32 bHasSpriteAnim3D : 1; // 0x040000 ANIM_3DS - 3DS animation (extension of 3D sprite)
uint32 bNoPreClipping : 1; // 0x080000 NO_PRE_CLIP - does not pre-clip the object (for large objects)
uint32 bHasZBuffer : 1; // 0x100000 OBJ_ZBUFFER - displays object in ZBuffer (exterior only!)
uint32 bHasZBufferInWater : 1; // 0x200000 OBJ_IN_WATER - displays object in ZBuffer in water (exterior only!)
};
/** Actors dynamic flags structure */
struct DynamicFlagsStruct {
uint32 bWaitHitFrame : 1; // 0x0001 WAIT_HIT_FRAME - wait for hit frame
uint32 bIsHitting : 1; // 0x0002 OK_HIT - hit frame anim
uint32 bAnimEnded : 1; // 0x0004 ANIM_END - anim ended in the current loop (will be looped in the next engine loop)
uint32 bAnimNewFrame : 1; // 0x0008 NEW_FRAME - new frame anim reached
uint32 bWasDrawn : 1; // 0x0010 WAS_DRAWN - actor has been drawn in this loop
uint32 bIsDead : 1; // 0x0020 OBJ_DEAD - is dead
uint32 bIsSpriteMoving : 1; // 0x0040 AUTO_STOP_DOOR - door is opening or closing (wait to reach the destination position)
uint32 bIsRotationByAnim : 1; // 0x0080 ANIM_MASTER_ROT - actor rotation is managed by its animation not by the engine
uint32 bIsFalling : 1; // 0x0100 FALLING - is falling on scene
uint32 bIsTargetable : 1; // 0x0200 IS_TARGETABLE (lba1) OK_SUPER_HIT (lba2)
uint32 bIsBlinking : 1; // 0x0400 IS_BLINKING (lba1) FRAME_SHIELD (lba2)
uint32 bWasWalkingBeforeFalling : 1; // 0x0800 DRAW_SHADOW (lba2) - bWasWalkingBeforeFalling in lba1
uint32 bANIM_MASTER_GRAVITY : 1; // 0x1000 ANIM_MASTER_GRAVITY (lba2)
uint32 bSKATING : 1; // 0x2000 SKATING (lba2) Ouch! I slip in a forbidden collision
uint32 bOK_RENVOIE : 1; // 0x4000 OK_RENVOIE (lba2) ready to send back a projectile
uint32 bLEFT_JUMP : 1; // 0x8000 LEFT_JUMP (lba2) ready to jump from the left foot
uint32 bRIGHT_JUMP : 1; // RIGHT_JUMP (1<<16) // (lba2) ready to jump from the right foot
uint32 bWAIT_SUPER_HIT : 1; // WAIT_SUPER_HIT (1<<17) // (lba2) waiting for the end of the animation before giving another super hit
uint32 bTRACK_MASTER_ROT : 1; // TRACK_MASTER_ROT (1<<18) // (lba2) it's the track that manages the direction
uint32 bFLY_JETPACK : 1; // FLY_JETPACK (1<<19) // (lba2) flying with the Jetpack
uint32 bDONT_PICK_CODE_JEU : 1; // DONT_PICK_CODE_JEU (1<<20) // (lba2) Cheat - Conveyor Belt Zones
uint32 bMANUAL_INTER_FRAME : 1; // MANUAL_INTER_FRAME (1<<21) // (lba2) Manually performs the ObjectSetInterFrame()
uint32 bWAIT_COORD : 1; // WAIT_COORD (1<<22) // (lba2) waiting to have been displayed to pass the coordinates from one point to an extra
uint32 bCHECK_FALLING : 1; // CHECK_FALLING (1<<23) // (lba2) forces object to test FALLING during a frame
};
/**
* Bonus type flags - a bitfield value, of which the bits mean:
* bit 8: clover leaf,
* bit 7: small key,
* bit 6: magic,
* bit 5: life,
* bit 4: money,
* If more than one type of bonus is selected, the actual type of bonus
* will be chosen randomly each time player uses Action.
*/
struct BonusParameter {
uint16 givenNothing : 1;
uint16 unk2 : 1; // unused in lba1
uint16 unk3 : 1; // unused in lba1
uint16 unk4 : 1; // unused in lba1
uint16 kashes : 1;
uint16 lifepoints : 1;
uint16 magicpoints : 1;
uint16 key : 1;
uint16 cloverleaf : 1;
uint16 unused : 7;
};
/**
* Actors structure
*
* Such as characters, doors, moving platforms, invisible actors, ...
*/
class ActorStruct { // T_OBJET
private:
ShapeType _col = ShapeType::kNone; // collision
bool _brickCausesDamage = false;
int32 _maxLife;
public:
ActorStruct(int maxLife = 0) : _lifePoint(maxLife), _maxLife(maxLife) {}
StaticFlagsStruct _flags;
DynamicFlagsStruct _workFlags;
EntityData _entityData;
inline ShapeType brickShape() const { return _col; }
inline void setCollision(ShapeType shapeType) {
_col = shapeType;
_brickCausesDamage = false;
}
inline void setBrickCausesDamage() { _brickCausesDamage = true; }
inline bool brickCausesDamage() { return _brickCausesDamage; }
void addLife(int32 val);
void setLife(int32 val);
bool isAttackWeaponAnimationActive() const;
bool isAttackAnimationActive() const;
bool isJumpAnimationActive() const;
const IVec3 &posObj() const;
int32 _body = -1; // costumeIndex - index into bodyTable
BodyType _genBody = BodyType::btNormal;
BodyType _saveGenBody = BodyType::btNormal; // lba2
AnimationTypes _genAnim = AnimationTypes::kAnimNone;
AnimationTypes _nextGenAnim = AnimationTypes::kStanding;
AnimationTypes _ptrAnimAction = AnimationTypes::kAnimNone;
int32 _sprite = 0;
EntityData *_entityDataPtr = nullptr;
int16 _actorIdx = 0; // own actor index
IVec3 _posObj; // PosObjX, PosObjY, PosObjZ
// T_ANIM_3DS - Coord.A3DS
struct A3DSAnim {
int32 Num;
int32 Deb;
int32 Fin;
} A3DS;
int32 _hitForce = 0;
int32 _hitBy = -1;
BonusParameter _bonusParameter;
int32 _beta = 0; // facing angle of actor. Minumum is 0 (SW). Going counter clock wise
int32 _srot = 40; // speed of rotation
ControlMode _move = ControlMode::kNoMove; // Move
int32 _delayInMillis = 0; // Info
int32 _cropLeft = 0; // Info
int32 _cropTop = 0; // Info1
int32 _cropRight = 0; // Info2
int32 _cropBottom = 0; // Info3
int32 _followedActor = 0; // same as Info3
int32 _bonusAmount = 0;
int32 _talkColor = COLOR_BLACK;
int32 _armor = 1;
int32 _lifePoint = 0;
/** Process actor coordinate Nxw, Nyw, Nzw */
IVec3 _processActor;
IVec3 _oldPos; // OldPosX, OldPosY, OldPosZ
int32 _offsetTrack = -1;
uint8 *_ptrTrack = nullptr;
int32 _moveScriptSize = 0;
int32 _offsetLife = 0;
int32 _saveOffsetLife = 0; // lba2
uint8 *_lifeScript = nullptr;
int32 _lifeScriptSize = 0;
int32 _labelTrack = 0; // script label index
int32 _offsetLabelTrack = 0; // pointer to LABEL offset
int32 _memoLabelTrack = 0;
/**
* colliding actor id
*/
int32 _objCol = -1;
/**
* actor id we are standing on
*/
int32 _carryBy = -1;
int32 _zoneSce = -1;
int32 _animStepBeta = 0;
IVec3 _animStep;
int32 _anim = -1;
int32 _doorWidth = 0;
int32 _frame = 0;
AnimType _flagAnim = AnimType::kAnimationTypeRepeat;
int32 _spriteActorRotation = 0;
uint8 _brickSound = 0U; // CodeJeu
int32 SampleAlways = 0; // lba2
uint8 SampleVolume = 0; // lba2
// SizeSHit contains the number of the brick under the wagon - hack
int16 SizeSHit; // lba2 - always square
// T_OBJ_3D Obj; // lba2
// T_GROUP_INFO CurrentFrame[30]; // lba2
BoundingBox _boundingBox; // Xmin, YMin, Zmin, Xmax, Ymax, Zmax
RealValue realAngle;
};
inline const IVec3 &ActorStruct::posObj() const {
return _posObj;
}
inline void ActorStruct::addLife(int32 val) {
setLife(_lifePoint + val);
}
inline void ActorStruct::setLife(int32 val) {
_lifePoint = val;
if (_lifePoint > _maxLife) {
_lifePoint = _maxLife;
}
}
class TwinEEngine;
class Actor {
private:
TwinEEngine *_engine;
/** Hero 3D entity for normal behaviour */
EntityData _heroEntityNORMAL;
/** Hero 3D entity for athletic behaviour */
EntityData _heroEntityATHLETIC;
/** Hero 3D entity for aggressive behaviour */
EntityData _heroEntityAGGRESSIVE;
/** Hero 3D entity for discrete behaviour */
EntityData _heroEntityDISCRETE;
/** Hero 3D entity for protopack behaviour */
EntityData _heroEntityPROTOPACK;
/**
* Initialize 3D actor body
* @param bodyIdx 3D actor body index
* @param actorIdx 3D actor index
*/
int32 searchBody(BodyType bodyIdx, int32 actorIdx, ActorBoundingBox &actorBoundingBox);
void loadBehaviourEntity(ActorStruct *actor, EntityData &entityData, int16 &bodyAnimIndex, int32 index);
void copyInterAnim(const BodyData &src, BodyData &dest);
public:
Actor(TwinEEngine *engine);
HeroBehaviourType _heroBehaviour = HeroBehaviourType::kNormal; // Comportement
HeroBehaviourType _saveHeroBehaviour = HeroBehaviourType::kNormal; // SaveComportementHero (lba2)
/** Hero auto aggressive mode */
bool _combatAuto = true;
/** Previous Hero behaviour */
HeroBehaviourType _previousHeroBehaviour = HeroBehaviourType::kNormal;
/** Previous Hero angle */
int16 _previousHeroAngle = 0;
int16 _cropBottomScreen = 0; // TODO: usage differ in original sources
/** Hero current anim for normal behaviour */
int16 _heroAnimIdxNORMAL = 0;
/** Hero current anim for athletic behaviour */
int16 _heroAnimIdxATHLETIC = 0;
/** Hero current anim for aggressive behaviour */
int16 _heroAnimIdxAGGRESSIVE = 0;
/** Hero current anim for discrete behaviour */
int16 _heroAnimIdxDISCRETE = 0;
/** Hero current anim for protopack behaviour */
int16 _heroAnimIdxPROTOPACK = 0;
/** Hero anim for behaviour menu */
int16 _heroAnimIdx[4];
void initSprite(int32 spriteNum, int32 actorIdx);
void setFrame(int32 actorIdx, uint32 frame);
/** Restart hero variables while opening new scenes */
void restartPerso();
/** Load hero 3D body and animations */
void loadHeroEntities();
TextId getTextIdForBehaviour() const;
/**
* Set hero behaviour
* @param behaviour behaviour value to set
*/
void setBehaviour(HeroBehaviourType behaviour); // SetComportement
/**
* Initialize 3D actor
* @param bodyIdx 3D actor body index
* @param actorIdx 3D actor index
*/
void initBody(BodyType bodyIdx, int16 actorIdx);
/**
* Initialize actors
* @param actorIdx actor index to init
*/
void startInitObj(int16 actorIdx);
/**
* Reset actor
* @param actorIdx actor index to init
*/
void initObject(int16 actorIdx);
/**
* Process hit actor
* @param actorIdx actor hitting index
* @param actorIdxAttacked actor attacked index
* @param strengthOfHit actor hitting strength of hit
* @param angle angle of actor hitting
*/
void hitObj(int32 actorIdx, int32 actorIdxAttacked, int32 strengthOfHit, int32 angle);
/** Process actor carrier */
void checkCarrier(int32 actorIdx);
/** Process actor extra bonus */
void giveExtraBonus(int32 actorIdx);
// Lba2
void posObjectAroundAnother(uint8 numsrc, uint8 numtopos); // PosObjetAroundAnother
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,848 @@
/* 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 "twine/scene/animations.h"
#include "common/util.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_state.h"
#include "twine/parser/anim.h"
#include "twine/parser/entity.h"
#include "twine/renderer/renderer.h"
#include "twine/resources/resources.h"
#include "twine/scene/collision.h"
#include "twine/scene/extra.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
namespace TwinE {
static const int32 magicLevelStrengthOfHit[] = {
MagicballStrengthType::kNoBallStrength,
MagicballStrengthType::kYellowBallStrength,
MagicballStrengthType::kGreenBallStrength,
MagicballStrengthType::kRedBallStrength,
MagicballStrengthType::kFireBallStrength,
0};
Animations::Animations(TwinEEngine *engine) : _engine(engine) {
}
int32 Animations::searchAnim(AnimationTypes animIdx, int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const int32 bodyAnimIndex = actor->_entityDataPtr->getAnimIndex(animIdx);
if (bodyAnimIndex != -1) {
_currentActorAnimExtraPtr = animIdx;
}
return bodyAnimIndex;
}
int16 Animations::patchInterAngle(int32 deltaTime, int32 keyFrameLength, int16 newAngle1, int16 lastAngle1) const {
const int16 lastAngle = ClampAngle(lastAngle1);
const int16 nextAngle = ClampAngle(newAngle1);
int16 angleDiff = nextAngle - lastAngle;
int16 computedAngle;
if (angleDiff) {
if (angleDiff < -LBAAngles::ANGLE_180) {
angleDiff += LBAAngles::ANGLE_360;
} else if (angleDiff > LBAAngles::ANGLE_180) {
angleDiff -= LBAAngles::ANGLE_360;
}
computedAngle = lastAngle + (angleDiff * deltaTime) / keyFrameLength;
} else {
computedAngle = lastAngle;
}
return ClampAngle(computedAngle);
}
int16 Animations::patchInterStep(int32 deltaTime, int32 keyFrameLength, int16 newPos, int16 lastPos) const {
int16 distance = newPos - lastPos;
int16 computedPos;
if (distance) {
computedPos = lastPos + (distance * deltaTime) / keyFrameLength;
} else {
computedPos = lastPos;
}
return computedPos;
}
bool Animations::doSetInterAnimObjet(int32 framedest, const AnimData &animData, BodyData &pBody, AnimTimerDataStruct *ptranimdest, bool global) {
if (!pBody.isAnimated()) {
return false;
}
const KeyFrame *keyFrame = animData.getKeyframe(framedest);
const int16 numBones = pBody.getNumBones();
int32 numOfBonesInAnim = animData.getNumBoneframes();
if (numOfBonesInAnim > numBones) {
numOfBonesInAnim = numBones;
}
const int32 timeDest = keyFrame->length;
const KeyFrame *lastKeyFramePtr = ptranimdest->ptr;
int32 remainingFrameTime = ptranimdest->time;
if (lastKeyFramePtr == nullptr) {
lastKeyFramePtr = keyFrame;
remainingFrameTime = timeDest;
}
const int32 time = _engine->timerRef - remainingFrameTime;
if (time >= timeDest) {
ptranimdest->ptr = keyFrame;
if (global) {
ptranimdest->time = _engine->timerRef;
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animMasterRot = keyFrame->animMasterRot;
_animStepAlpha = ToAngle(keyFrame->animStepAlpha);
_animStepBeta = ToAngle(keyFrame->animStepBeta);
_animStepGamma = ToAngle(keyFrame->animStepGamma);
}
copyKeyFrameToState(keyFrame, pBody, numOfBonesInAnim);
return true;
}
if (global) {
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animMasterRot = keyFrame->animMasterRot;
_animStepAlpha = (keyFrame->animStepAlpha * time) / timeDest;
_animStepBeta = (keyFrame->animStepBeta * time) / timeDest;
_animStepGamma = (keyFrame->animStepGamma * time) / timeDest;
}
if (numOfBonesInAnim <= 1) {
return false;
}
int16 boneIdx = 1;
int16 tmpNumOfPoints = MIN<int16>(lastKeyFramePtr->boneframes.size() - 1, numOfBonesInAnim - 1);
do {
BoneFrame *boneState = pBody.getBoneState(boneIdx);
const BoneFrame &boneFrame = keyFrame->boneframes[boneIdx];
const BoneFrame &lastBoneFrame = lastKeyFramePtr->boneframes[boneIdx];
boneState->type = boneFrame.type;
switch (boneFrame.type) {
case BoneType::TYPE_ROTATE:
boneState->x = patchInterAngle(time, timeDest, boneFrame.x, lastBoneFrame.x);
boneState->y = patchInterAngle(time, timeDest, boneFrame.y, lastBoneFrame.y);
boneState->z = patchInterAngle(time, timeDest, boneFrame.z, lastBoneFrame.z);
break;
case BoneType::TYPE_TRANSLATE:
case BoneType::TYPE_ZOOM:
boneState->x = patchInterStep(time, timeDest, boneFrame.x, lastBoneFrame.x);
boneState->y = patchInterStep(time, timeDest, boneFrame.y, lastBoneFrame.y);
boneState->z = patchInterStep(time, timeDest, boneFrame.z, lastBoneFrame.z);
break;
default:
error("Unsupported animation rotation mode %d", boneFrame.type);
}
++boneIdx;
} while (--tmpNumOfPoints);
return false;
}
void Animations::setAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
if (!bodyData.isAnimated()) {
return;
}
const int32 numOfKeyframeInAnim = animData.getKeyframes().size();
if (keyframeIdx < 0 || keyframeIdx >= numOfKeyframeInAnim) {
return;
}
const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx);
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animMasterRot = keyFrame->animMasterRot;
_animStepBeta = ToAngle(keyFrame->animStepBeta);
animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx);
animTimerDataPtr->time = _engine->timerRef;
const int16 numBones = bodyData.getNumBones();
int16 numOfBonesInAnim = animData.getNumBoneframes();
if (numOfBonesInAnim > numBones) {
numOfBonesInAnim = numBones;
}
copyKeyFrameToState(keyFrame, bodyData, numOfBonesInAnim);
}
void Animations::stockInterAnim(const BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
if (!bodyData.isAnimated()) {
return;
}
if (_animKeyframeBufIdx >= ARRAYSIZE(_animKeyframeBuf)) {
_animKeyframeBufIdx = 0;
}
animTimerDataPtr->time = _engine->timerRef;
KeyFrame *keyframe = &_animKeyframeBuf[_animKeyframeBufIdx++];
animTimerDataPtr->ptr = keyframe;
copyStateToKeyFrame(keyframe, bodyData);
}
void Animations::copyStateToKeyFrame(KeyFrame *keyframe, const BodyData &bodyData) const {
const int32 numBones = bodyData.getNumBones();
keyframe->boneframes.clear();
keyframe->boneframes.reserve(numBones);
for (int32 i = 0; i < numBones; ++i) {
const BoneFrame *boneState = bodyData.getBoneState(i);
keyframe->boneframes.push_back(*boneState);
}
}
void Animations::copyKeyFrameToState(const KeyFrame *keyframe, BodyData &bodyData, int32 numBones) const {
for (int32 i = 0; i < numBones; ++i) {
BoneFrame *boneState = bodyData.getBoneState(i);
*boneState = keyframe->boneframes[i];
}
}
bool Animations::setInterDepObjet(int32 keyframeIdx, const AnimData &animData, AnimTimerDataStruct *animTimerDataPtr) {
const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx);
const int32 timeDest = keyFrame->length;
int32 remainingFrameTime = animTimerDataPtr->time;
if (animTimerDataPtr->ptr == nullptr) {
remainingFrameTime = timeDest;
}
const int32 time = _engine->timerRef - remainingFrameTime;
_animMasterRot = keyFrame->animMasterRot;
if (time >= timeDest) {
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animStepAlpha = ToAngle(keyFrame->animStepAlpha);
_animStepBeta = ToAngle(keyFrame->animStepBeta);
_animStepGamma = ToAngle(keyFrame->animStepGamma);
animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx);
animTimerDataPtr->time = _engine->timerRef;
return true; // finished animation
}
_animStep.x = (keyFrame->x * time) / timeDest;
_animStep.y = (keyFrame->y * time) / timeDest;
_animStep.z = (keyFrame->z * time) / timeDest;
_animStepAlpha = ToAngle((keyFrame->animStepAlpha * time) / timeDest);
_animStepBeta = ToAngle((keyFrame->animStepBeta * time) / timeDest);
_animStepGamma = ToAngle((keyFrame->animStepGamma * time) / timeDest);
return false;
}
void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_entityDataPtr == nullptr || actor->_ptrAnimAction == AnimationTypes::kAnimNone) {
return;
}
const Common::Array<EntityAnim::Action> *actions = actor->_entityDataPtr->getActions(actor->_ptrAnimAction);
if (actions == nullptr) {
return;
}
for (const EntityAnim::Action &action : *actions) {
debugC(1, TwinE::kDebugAnimation, "Execute animation action %d for actor %d", (int)action.type, actorIdx);
switch (action.type) {
case ActionType::ACTION_HITTING:
if (action.animFrame - 1 == actor->_frame) {
actor->_hitForce = action.strength;
actor->_workFlags.bIsHitting = 1;
}
break;
case ActionType::ACTION_SAMPLE:
if (action.animFrame == actor->_frame) {
_engine->_sound->mixSample3D(action.sampleIndex, 0x1000, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_SAMPLE_FREQ:
if (action.animFrame == actor->_frame) {
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(action.frequency) - (action.frequency / 2);
_engine->_sound->mixSample3D(action.sampleIndex, pitchBend, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_THROW_EXTRA_BONUS:
if (action.animFrame == actor->_frame) {
_engine->_extra->throwExtra(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.spriteIndex, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_MAGIC_BALL:
if (_engine->_gameState->_magicBall == -1 && action.animFrame == actor->_frame) {
_engine->_extra->addExtraThrowMagicball(actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle);
}
break;
case ActionType::ACTION_SAMPLE_REPEAT:
if (action.animFrame == actor->_frame) {
_engine->_sound->mixSample3D(action.sampleIndex, 0x1000, action.repeat, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_THROW_SEARCH:
if (action.animFrame == actor->_frame) {
_engine->_extra->addExtraAiming(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.spriteIndex, action.targetActor, action.finalAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_ALPHA:
if (action.animFrame == actor->_frame) {
_engine->_extra->throwExtra(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.spriteIndex, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_SAMPLE_STOP:
if (action.animFrame == actor->_frame) {
_engine->_sound->stopSample(action.sampleIndex);
}
break;
case ActionType::ACTION_LEFT_STEP:
if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorBegin;
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
_engine->_sound->mixSample3D(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_RIGHT_STEP:
if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorRightBegin;
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
_engine->_sound->mixSample3D(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_HERO_HITTING:
if (action.animFrame - 1 == actor->_frame) {
actor->_hitForce = magicLevelStrengthOfHit[_engine->_gameState->_magicLevelIdx];
actor->_workFlags.bIsHitting = 1;
}
break;
case ActionType::ACTION_THROW_3D:
if (action.animFrame == actor->_frame) {
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 throwX = destPos.x + actor->_posObj.x;
const int32 throwY = action.distanceY + actor->_posObj.y;
const int32 throwZ = destPos.y + actor->_posObj.z;
_engine->_extra->throwExtra(actorIdx, throwX, throwY, throwZ, action.spriteIndex,
action.xAngle, action.yAngle + actor->_beta, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_ALPHA:
if (action.animFrame == actor->_frame) {
const int32 distance = getDistance2D(actor->posObj(), _engine->_scene->_sceneHero->posObj());
const int32 newAngle = _engine->_movements->getAngle(actor->_posObj.y, 0, _engine->_scene->_sceneHero->_posObj.y, distance);
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 throwX = destPos.x + actor->_posObj.x;
const int32 throwY = action.distanceY + actor->_posObj.y;
const int32 throwZ = destPos.y + actor->_posObj.z;
_engine->_extra->throwExtra(actorIdx, throwX, throwY, throwZ, action.spriteIndex,
action.xAngle + newAngle, action.yAngle + actor->_beta, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_SEARCH:
if (action.animFrame == actor->_frame) {
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 x = actor->_posObj.x + destPos.x;
const int32 y = actor->_posObj.y + action.distanceY;
const int32 z = actor->_posObj.z + destPos.y;
_engine->_extra->addExtraAiming(actorIdx, x, y, z, action.spriteIndex,
action.targetActor, action.finalAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_MAGIC:
if (_engine->_gameState->_magicBall == -1 && action.animFrame == actor->_frame) {
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 x = actor->_posObj.x + destPos.x;
const int32 y = actor->_posObj.y + action.distanceY;
const int32 z = actor->_posObj.z + destPos.y;
_engine->_extra->addExtraThrowMagicball(x, y, z, action.xAngle, actor->_beta, action.yAngle, action.finalAngle);
}
break;
case ActionType::ACTION_ZV:
default:
break;
}
}
}
bool Animations::initAnim(AnimationTypes genNewAnim, AnimType flag, AnimationTypes genNextAnim, int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return false;
}
if (actor->_flags.bSprite3D) {
return false;
}
if (genNewAnim == actor->_genAnim && actor->_anim != -1) {
return true;
}
if (genNextAnim == AnimationTypes::kNoAnim && actor->_flagAnim != AnimType::kAnimationAllThen) {
genNextAnim = actor->_genAnim;
}
int32 newanim = searchAnim(genNewAnim, actorIdx);
if (newanim == -1) {
newanim = searchAnim(AnimationTypes::kStanding, actorIdx);
if (newanim == -1) {
error("Could not find anim index for 'standing' (actor %i)", actorIdx);
}
}
if (flag != AnimType::kAnimationSet && actor->_flagAnim == AnimType::kAnimationAllThen) {
actor->_nextGenAnim = genNewAnim;
return false;
}
if (flag == AnimType::kAnimationInsert) {
flag = AnimType::kAnimationAllThen;
genNextAnim = actor->_genAnim;
if (genNextAnim == AnimationTypes::kThrowBall || genNextAnim == AnimationTypes::kFall || genNextAnim == AnimationTypes::kLanding || genNextAnim == AnimationTypes::kLandingHit) {
genNextAnim = AnimationTypes::kStanding;
}
}
if (flag == AnimType::kAnimationSet) {
flag = AnimType::kAnimationAllThen;
}
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
if (actor->_anim == -1) {
// if no previous animation
setAnimObjet(0, _engine->_resources->_animData[newanim], bodyData, &bodyData._animTimerData);
} else {
// interpolation between animations
stockInterAnim(bodyData, &bodyData._animTimerData);
}
actor->_anim = newanim;
actor->_genAnim = genNewAnim;
actor->_nextGenAnim = genNextAnim;
actor->_ptrAnimAction = _currentActorAnimExtraPtr;
actor->_flagAnim = flag;
actor->_frame = 0;
actor->_workFlags.bIsHitting = 0;
actor->_workFlags.bAnimEnded = 0;
actor->_workFlags.bAnimNewFrame = 1;
processAnimActions(actorIdx);
actor->_animStepBeta = LBAAngles::ANGLE_0;
actor->_animStep = IVec3();
debugC(1, TwinE::kDebugAnimation, "Change animation for actor %d to %d", actorIdx, newanim);
return true;
}
void Animations::doAnim(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return;
}
const IVec3 &oldPos = actor->_oldPos;
IVec3 &processActor = actor->_processActor;
if (actor->_flags.bSprite3D) {
if (actor->_hitForce) {
actor->_workFlags.bIsHitting = 1;
}
processActor = actor->posObj();
if (!actor->_workFlags.bIsFalling) {
if (actor->_srot) {
int32 xAxisRotation = actor->realAngle.getRealValueFromTime(_engine->timerRef);
if (!xAxisRotation) {
if (actor->realAngle.endValue > 0) {
xAxisRotation = 1;
} else {
xAxisRotation = -1;
}
}
const IVec2 xRotPos = _engine->_renderer->rotate(xAxisRotation, 0, actor->_spriteActorRotation);
processActor.y = actor->_posObj.y - xRotPos.y;
const IVec2 destPos = _engine->_renderer->rotate(0, xRotPos.x, actor->_beta);
processActor.x = actor->_posObj.x + destPos.x;
processActor.z = actor->_posObj.z + destPos.y;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, actor->_srot, LBAAngles::ANGLE_17, &actor->realAngle);
if (actor->_workFlags.bIsSpriteMoving) {
if (actor->_doorWidth) { // open door
if (getDistance2D(processActor.x, processActor.z, actor->_animStep.x, actor->_animStep.z) >= actor->_doorWidth) {
if (actor->_beta == LBAAngles::ANGLE_0) { // down
processActor.z = actor->_animStep.z + actor->_doorWidth;
} else if (actor->_beta == LBAAngles::ANGLE_90) { // right
processActor.x = actor->_animStep.x + actor->_doorWidth;
} else if (actor->_beta == LBAAngles::ANGLE_180) { // up
processActor.z = actor->_animStep.z - actor->_doorWidth;
} else if (actor->_beta == LBAAngles::ANGLE_270) { // left
processActor.x = actor->_animStep.x - actor->_doorWidth;
}
actor->_workFlags.bIsSpriteMoving = 0;
actor->_srot = 0;
}
} else { // close door
bool updatePos = false;
if (actor->_beta == LBAAngles::ANGLE_0) { // down
if (processActor.z <= actor->_animStep.z) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::ANGLE_90) { // right
if (processActor.x <= actor->_animStep.x) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::ANGLE_180) { // up
if (processActor.z >= actor->_animStep.z) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::ANGLE_270) { // left
if (processActor.x >= actor->_animStep.x) {
updatePos = true;
}
}
if (updatePos) {
processActor = actor->_animStep;
actor->_workFlags.bIsSpriteMoving = 0;
actor->_srot = 0;
}
}
}
}
if (actor->_flags.bCanBePushed) {
processActor += actor->_animStep;
if (actor->_flags.bUseMiniZv) {
processActor.x = ((processActor.x / (SIZE_BRICK_XZ / 4)) * (SIZE_BRICK_XZ / 4));
processActor.z = ((processActor.z / (SIZE_BRICK_XZ / 4)) * (SIZE_BRICK_XZ / 4));
}
actor->_animStep = IVec3();
}
}
} else { // 3D actor
if (actor->_anim != -1) {
const AnimData &animData = _engine->_resources->_animData[actor->_anim];
bool keyFramePassed = false;
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
if (bodyData.isAnimated()) {
keyFramePassed = setInterDepObjet(actor->_frame, animData, &bodyData._animTimerData);
}
if (_animMasterRot) {
actor->_workFlags.bIsRotationByAnim = 1;
} else {
actor->_workFlags.bIsRotationByAnim = 0;
}
actor->_beta = ClampAngle(actor->_beta + _animStepBeta - actor->_animStepBeta);
actor->_animStepBeta = _animStepBeta;
const IVec2 &destPos = _engine->_renderer->rotate(_animStep.x, _animStep.z, actor->_beta);
_animStep.x = destPos.x;
_animStep.z = destPos.y;
processActor = actor->posObj() + _animStep - actor->_animStep;
actor->_animStep = _animStep;
actor->_workFlags.bAnimEnded = 0;
actor->_workFlags.bAnimNewFrame = 0;
if (keyFramePassed) {
actor->_frame++;
actor->_workFlags.bAnimNewFrame = 1;
// if actor have animation actions to process
processAnimActions(actorIdx);
int16 numKeyframe = actor->_frame;
if (numKeyframe == (int16)animData.getNbFramesAnim()) {
actor->_workFlags.bIsHitting = 0;
if (actor->_flagAnim == AnimType::kAnimationTypeRepeat) {
actor->_frame = animData.getLoopFrame();
} else {
actor->_genAnim = actor->_nextGenAnim;
actor->_anim = searchAnim(actor->_genAnim, actorIdx);
if (actor->_anim == -1) {
actor->_anim = searchAnim(AnimationTypes::kStanding, actorIdx);
actor->_genAnim = AnimationTypes::kStanding;
}
actor->_ptrAnimAction = _currentActorAnimExtraPtr;
actor->_flagAnim = AnimType::kAnimationTypeRepeat;
actor->_frame = 0;
actor->_hitForce = 0;
}
processAnimActions(actorIdx);
actor->_workFlags.bAnimEnded = 1;
}
actor->_animStepBeta = LBAAngles::ANGLE_0;
actor->_animStep = IVec3();
}
}
}
Collision* collision = _engine->_collision;
// actor standing on another actor
if (actor->_carryBy != -1) {
const ActorStruct *standOnActor = _engine->_scene->getActor(actor->_carryBy);
processActor -= standOnActor->_oldPos;
processActor += standOnActor->posObj();
if (!collision->checkZvOnZv(actorIdx, actor->_carryBy)) {
actor->_carryBy = -1; // no longer standing on other actor
}
}
// actor falling Y speed
if (actor->_workFlags.bIsFalling) {
processActor = oldPos;
processActor.y += _engine->_stepFalling; // add step to fall
}
// actor collisions with bricks
uint32 col1 = 0; /** Cause damage in current processed actor */
if (actor->_flags.bComputeCollisionWithBricks) {
ShapeType col = _engine->_grid->worldColBrick(oldPos);
if (col != ShapeType::kNone) {
if (col == ShapeType::kSolid) {
processActor.y = (processActor.y / SIZE_BRICK_Y) * SIZE_BRICK_Y + SIZE_BRICK_Y; // go upper
actor->_posObj.y = processActor.y;
} else {
collision->reajustPos(processActor, col);
}
}
if (actor->_flags.bComputeCollisionWithObj) {
collision->checkObjCol(actorIdx);
}
if (actor->_carryBy != -1 && actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->setCollisionPos(processActor);
if (IS_HERO(actorIdx) && !actor->_flags.bComputeLowCollision) {
// check hero collisions with bricks
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1);
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2);
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4);
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8);
} else {
// check other actors collisions with bricks
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1);
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2);
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4);
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8);
}
// process wall hit while running
if (col1 && !actor->_workFlags.bIsFalling && IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour == HeroBehaviourType::kAthletic && actor->_genAnim == AnimationTypes::kForward) {
IVec2 destPos = _engine->_renderer->rotate(actor->_boundingBox.mins.x, actor->_boundingBox.mins.z, actor->_beta + LBAAngles::ANGLE_315 + LBAAngles::ANGLE_180);
destPos.x += processActor.x;
destPos.y += processActor.z;
if (destPos.x >= 0 && destPos.y >= 0 && destPos.x <= SCENE_SIZE_MAX && destPos.y <= SCENE_SIZE_MAX) {
if (_engine->_grid->worldColBrick(destPos.x, processActor.y + SIZE_BRICK_Y, destPos.y) != ShapeType::kNone && _engine->_cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->initSpecial(actor->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
if (IS_HERO(actorIdx)) {
_engine->_movements->_lastJoyFlag = true;
}
actor->addLife(-1);
}
}
}
col = _engine->_grid->worldColBrick(processActor);
actor->setCollision(col);
if (col != ShapeType::kNone) {
if (col == ShapeType::kSolid) {
if (actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
processActor.y = (collision->_collision.y * SIZE_BRICK_Y) + SIZE_BRICK_Y;
} else {
if (IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour == HeroBehaviourType::kAthletic && actor->_genAnim == AnimationTypes::kForward && _engine->_cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->initSpecial(actor->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
_engine->_movements->_lastJoyFlag = true;
actor->addLife(-1);
}
// no Z coordinate issue
if (_engine->_grid->worldColBrick(processActor.x, processActor.y, oldPos.z) != ShapeType::kNone) {
if (_engine->_grid->worldColBrick(oldPos.x, processActor.y, processActor.z) != ShapeType::kNone) {
return;
} else {
processActor.x = oldPos.x;
}
} else {
processActor.z = oldPos.z;
}
}
} else {
if (actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->reajustPos(processActor, col);
}
if (actor->_workFlags.bIsFalling) {
debugC(1, TwinE::kDebugCollision, "Actor %d reset falling", actorIdx);
}
actor->_workFlags.bIsFalling = 0;
} else {
if (actor->_flags.bObjFallable && actor->_carryBy == -1) {
col = _engine->_grid->worldColBrick(processActor.x, processActor.y - 1, processActor.z);
if (col != ShapeType::kNone) {
if (actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->reajustPos(processActor, col);
} else {
if (!actor->_workFlags.bIsRotationByAnim) {
debugC(1, TwinE::kDebugCollision, "Actor %d is falling", actorIdx);
actor->_workFlags.bIsFalling = 1;
if (IS_HERO(actorIdx) && _engine->_scene->_startYFalling == 0) {
_engine->_scene->_startYFalling = processActor.y;
int32 y = processActor.y - 1 - SIZE_BRICK_Y;
while (y > 0 && ShapeType::kNone == _engine->_grid->worldColBrick(processActor.x, y, processActor.z)) {
y -= SIZE_BRICK_Y;
}
y = (y + SIZE_BRICK_Y) & ~(SIZE_BRICK_Y - 1);
int32 fallHeight = processActor.y - y;
if (fallHeight <= (2 * SIZE_BRICK_Y) && actor->_genAnim == AnimationTypes::kForward) {
actor->_workFlags.bWasWalkingBeforeFalling = 1;
} else {
initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
} else {
initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
}
}
}
}
// if under the map, than die
if (collision->_collision.y == -1) {
actor->setLife(0);
}
} else {
if (actor->_flags.bComputeCollisionWithObj) {
collision->checkObjCol(actorIdx);
}
}
if (col1) {
actor->setBrickCausesDamage();
}
// check and fix actor bounding position
if (processActor.x < 0) {
processActor.x = 0;
}
if (processActor.y < 0) {
processActor.y = 0;
}
if (processActor.z < 0) {
processActor.z = 0;
}
if (processActor.x > SCENE_SIZE_MAX) {
processActor.x = SCENE_SIZE_MAX;
}
if (processActor.z > SCENE_SIZE_MAX) {
processActor.z = SCENE_SIZE_MAX;
}
actor->_posObj = processActor;
}
} // namespace TwinE

View File

@@ -0,0 +1,133 @@
/* 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 TWINE_SCENE_ANIMATIONS_H
#define TWINE_SCENE_ANIMATIONS_H
#include "common/scummsys.h"
#include "twine/parser/anim.h"
namespace TwinE {
struct AnimTimerDataStruct;
class BodyData;
class TwinEEngine;
class Animations {
private:
TwinEEngine *_engine;
int16 patchInterAngle(int32 deltaTime, int32 keyFrameLength, int16 newAngle1, int16 lastAngle1) const;
int16 patchInterStep(int32 deltaTime, int32 keyFrameLength, int16 newPos, int16 lastPos) const;
/**
* Verify animation at keyframe
* @param keyframeIdx Animation key frame index
* @param animData Animation data
* @param animTimerDataPtr Animation time data
*/
bool setInterDepObjet(int32 keyframeIdx, const AnimData &animData, AnimTimerDataStruct *animTimerDataPtr);
void copyKeyFrameToState(const KeyFrame *keyframe, BodyData &bodyData, int32 numBones) const;
void copyStateToKeyFrame(KeyFrame *keyframe, const BodyData &bodyData) const;
bool doSetInterAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPt, bool global);
int _animKeyframeBufIdx = 0;
KeyFrame _animKeyframeBuf[32];
/** Rotation by anim and not by engine */
int16 _animMasterRot = 0;
/** Last rotation angle */
int16 _animStepBeta = 0;
int16 _animStepAlpha = 0;
int16 _animStepGamma = 0;
/** Current step coordinates */
IVec3 _animStep;
public:
Animations(TwinEEngine *engine);
/** Current actor anim extra pointer */
AnimationTypes _currentActorAnimExtraPtr = AnimationTypes::kAnimNone;
/**
* Set animation keyframe
* @param keyframIdx Animation keyframe index
* @param animData Animation data
* @param bodyData Body model data
* @param animTimerDataPtr Animation time data
*/
void setAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr);
/**
* Set new body animation
* @param keyframeIdx Animation key frame index
* @param animData Animation data
* @param bodyData Body model data
* @param animTimerDataPtr Animation time data
*/
bool setInterAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
return doSetInterAnimObjet(keyframeIdx, animData, bodyData, animTimerDataPtr, true);
}
bool setInterAnimObjet2(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
return doSetInterAnimObjet(keyframeIdx, animData, bodyData, animTimerDataPtr, false);
}
/**
* Get entity anim index (This is taken from File3D entities)
* @param animIdx Entity animation index
* @param actorIdx Actor index
*/
int32 searchAnim(AnimationTypes animIdx, int32 actorIdx = OWN_ACTOR_SCENE_INDEX);
/**
* Stock animation - copy the next keyFrame from a different buffer
* @param bodyData Body model data
* @param animTimerDataPtr Animation time data
*/
void stockInterAnim(const BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr);
/**
* Initialize animation
* @param newAnim animation to init
* @param animType animation type
* @param animExtra animation actions extra data
* @param actorIdx actor index
*/
bool initAnim(AnimationTypes newAnim, AnimType animType, AnimationTypes animExtra, int32 actorIdx); // InitAnim
/**
* Process acotr animation actions
* @param actorIdx Actor index
*/
void processAnimActions(int32 actorIdx);
/**
* Process main loop actor animations
* @param actorIdx Actor index
*/
void doAnim(int32 actorIdx);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,709 @@
/* 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 "twine/scene/buggy.h"
#include "twine/audio/sound.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
#define MAX_SAMPLE_PITCH 11000
#define MIN_SAMPLE_PITCH2 5000
#define MAX_SAMPLE_PITCH2 8500
#define MAX_SPEED 3800
#define TEMPO_GEAR 1200 // speed change
#define SAMPLE_BUGGY 109
namespace TwinE {
void Buggy::initBuggy(uint8 numobj, uint32 flaginit) {
S_BUGGY *ptb = &ListBuggy[0];
ActorStruct *ptrobj = _engine->_scene->getActor(numobj);
// So that the objects follow their tracks without being interrupted
// by the buggy (too bad, it will be pushed)
ptrobj->_flags.bCanBePushed = true;
ptrobj->_flags.bCanDrown = true;
if (flaginit == 2 // we force the repositioning of the buggy
|| (flaginit && !NumBuggy)) // first initialization
// because the empty buggy cannot be Twinsen
{
ptb->Cube = _engine->_scene->_numCube; // Port-Ludo (Desert)
ptb->X = ptrobj->_posObj.x;
ptb->Y = ptrobj->_posObj.y;
ptb->Z = ptrobj->_posObj.z;
ptb->Beta = ptrobj->_beta;
_engine->_actor->initBody(BodyType::btNormal, numobj);
NumBuggy = (uint8)(numobj | BUGGY_PRESENT);
} else if (NumBuggy) {
if (_engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX)->_move != ControlMode::kBuggyManual && _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX)->_move != ControlMode::kBuggy) {
int32 x, y;
if (_engine->_scene->_numCube == ptb->Cube) {
ptrobj->_posObj.x = ptb->X;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z;
ptrobj->_beta = ptb->Beta;
_engine->_actor->initBody(BodyType::btNormal, numobj);
} else if (_engine->_scene->loadSceneCubeXY(ptb->Cube, &x, &y)) {
x -= _engine->_scene->_currentCubeX;
y -= _engine->_scene->_currentCubeY;
ptrobj->_posObj.x = ptb->X + x * 32768;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z + y * 32768;
ptrobj->_beta = ptb->Beta;
ptrobj->_flags.bNoShadow = 1;
ptrobj->_flags.bIsBackgrounded = 1;
ptrobj->_flags.bNoElectricShock = 1;
ptrobj->_flags.bHasZBuffer = 1;
_engine->_actor->initBody(BodyType::btNormal, numobj);
} else {
_engine->_actor->initBody(BodyType::btNone, numobj);
}
_engine->_movements->clearRealAngle(ptrobj);
} else {
_engine->_actor->initBody(BodyType::btNone, numobj);
}
NumBuggy = (uint8)(numobj | BUGGY_PRESENT);
} else {
_engine->_actor->initBody(BodyType::btNone, numobj);
}
}
void Buggy::resetBuggy() {
S_BUGGY *ptb = &ListBuggy[0];
NumBuggy = 0;
ptb->Cube = 0;
}
void Buggy::takeBuggy() {
int32 sample;
ActorStruct *ptrobj = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
S_BUGGY *ptb = &ListBuggy[0];
ptb->SpeedRot = 1024;
// TODO: ptb->LastTimer = TimerRefHR;
// TODO: ObjectClear(&ptrobj);
// Shielding in case the Buggy moved (being pushed, for example).
ptb->X = _engine->_scene->getActor(NUM_BUGGY)->_posObj.x;
ptb->Y = _engine->_scene->getActor(NUM_BUGGY)->_posObj.y;
ptb->Z = _engine->_scene->getActor(NUM_BUGGY)->_posObj.z;
ptrobj->_posObj.x = ptb->X;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z;
ptrobj->_beta = ptb->Beta;
_engine->_movements->clearRealAngle(ptrobj); // To avoid crushing the beta.
ptrobj->_workFlags.bMANUAL_INTER_FRAME = true;
ptrobj->_flags.bHasZBuffer = true;
// TODO: _engine->_actor->setBehaviour(HeroBehaviourType::kBUGGY);
// Switch Buggy Scenario to NoBody.
_engine->_actor->initBody(BodyType::btNone, NUM_BUGGY);
if (ptrobj->SampleAlways) {
_engine->_sound->stopSample(ptrobj->SampleAlways);
ptrobj->SampleAlways = 0;
}
sample = SAMPLE_BUGGY;
if (_engine->_sound->isSamplePlaying(sample)) {
_engine->_sound->stopSample(sample);
}
ptrobj->SampleVolume = 20;
// TODO: ParmSampleVolume = ptrobj->SampleVolume;
Gear = 0;
TimerGear = 0;
// TODO: ptrobj->SampleAlways = _engine->_sound->playSample(SAMPLE_BUGGY, 4096, 0, 0,
// ptrobj->_posObj.x, ptrobj->_posObj.y, ptrobj->_posObj.z);
}
#if 0
static void ObjectClear(T_OBJ *ptb3d) {
memset(ptb3d, 0, sizeof(T_OBJ));
ptb3d.OBJ_3D.Body, -1
ptb3d.OBJ_3D.NextBody, -1
ptb3d.OBJ_3D.Texture, -1
ptb3d.OBJ_3D.NextTexture, -1
ptb3d.OBJ_3D.Anim, -1
}
#endif
void Buggy::leaveBuggy(HeroBehaviourType behaviour) {
int32 sample;
ActorStruct *ptrobj = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
S_BUGGY *ptb = &ListBuggy[0];
sample = SAMPLE_BUGGY;
if (_engine->_sound->isSamplePlaying(sample)) {
_engine->_sound->stopSample(sample);
ptrobj->SampleAlways = 0;
}
ptb->X = ptrobj->_posObj.x;
ptb->Y = ptrobj->_posObj.y;
ptb->Z = ptrobj->_posObj.z;
ptb->Beta = ptrobj->_beta;
ptb->Cube = _engine->_scene->_numCube;
// TODO: ObjectClear(ptrobj);
ptrobj->_workFlags.bMANUAL_INTER_FRAME = 0;
ptrobj->_flags.bHasZBuffer = 0;
_engine->_actor->initBody(BodyType::btTunic, OWN_ACTOR_SCENE_INDEX);
_engine->_actor->setBehaviour(behaviour);
// Restore scenario buggy.
ptrobj = _engine->_scene->getActor(NUM_BUGGY);
ptrobj->_posObj.x = ptb->X;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z;
ptrobj->_beta = ptb->Beta;
ptrobj->_brickSound = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX)->_brickSound;
_engine->_movements->clearRealAngle(ptrobj); // To avoid crushing the beta
_engine->_actor->initBody(BodyType::btNormal, NUM_BUGGY);
// Search for a free position for Twinsen nearby.
_engine->_actor->posObjectAroundAnother(NUM_BUGGY, OWN_ACTOR_SCENE_INDEX);
}
#if 0
struct T_HALF_POLY {
uint32 Bank : 4; // coul bank poly
uint32 TexFlag : 2; // flag texture 00 rien 01 triste 10 flat 11 gouraud
uint32 PolyFlag : 2; // flag poly 00 rien 01 flat 10 gouraud 11 dither
uint32 SampleStep : 4; // sample pas twinsen
uint32 CodeJeu : 4; // code jeu
uint32 Sens : 1; // sens diagonale
uint32 Col : 1;
uint32 Dummy : 1;
uint32 IndexTex : 13; // index texture 8192
}; // 1 long
struct T_HALF_TEX {
uint16 Tx0;
uint16 Ty0;
uint16 Tx1;
uint16 Ty1;
uint16 Tx2;
uint16 Ty2;
}; // 2 Longs
int32 CalculAltitudeObjet(int32 x, int32 z, int32 cj) {
int32 y0, y1, y2, y3;
int32 dx, dz;
int32 dz0, dz1;
dx = x >> 9; // div512
dz = z >> 9; // div512
if ((dx < 0) || (dx > 63))
return -1;
if ((dz < 0) || (dz > 63))
return -1;
x &= 511;
z &= 511;
dz0 = dz * 65;
dz1 = dz0 + 65;
//---------------------------------------------------------------
if (cj == -1) {
T_HALF_POLY *mappoly = &MapPolyGround[dz * 64 * 2 + dx * 2];
if (mappoly->Sens == 0) // poly séparé par ligne reliant point 0 et 2
{
if (x >= z) // poly de droite
{
mappoly++;
}
} else // poly séparé par ligne reliant point 1 et 3
{
if (511 - x <= z) // poly de droite
{
mappoly++;
}
}
cj = (uint8)mappoly->CodeJeu;
}
//--------------------------------------------------------------
y0 = MapSommetY[dz0 + dx];
y1 = MapSommetY[dz1 + dx];
y2 = MapSommetY[dz1 + (dx + 1)];
y3 = MapSommetY[dz0 + (dx + 1)];
if (cj == CJ_FOOT_WATER OR cj == CJ_WATER) {
uint8 *i = &MapIntensity[dz0 + dx];
y0 += (i[0] >> 4) * -200;
y1 += (i[65] >> 4) * -200;
y2 += (i[65 + 1] >> 4) * -200;
y3 += (i[1] >> 4) * -200;
}
if (MapPolyGround[dz * 64 * 2 + dx * 2].Sens == 0) // poly séparé par ligne reliant point 0 et 2
{
if (x < z) // poly de gauche
{
return (y0 + ((y1 - y0) * z + (y2 - y1) * x) / 512);
} else // poly de droite
{
return (y0 + ((y3 - y0) * x + (y2 - y3) * z) / 512);
}
} else // poly séparé par ligne reliant point 1 et 3
{
if (511 - x > z) // poly de gauche
{
return (y0 + ((y3 - y0) * x + (y1 - y0) * z) / 512);
} else // poly de droite
{
return (y1 + ((y2 - y1) * x + (y3 - y2) * (511 - z)) / 512);
}
}
}
#endif
void Buggy::doAnimBuggy(ActorStruct *ptrobj) {
#if 0
int32 x1, y1, z1, yw;
S_BUGGY *ptb = &ListBuggy[0];
T_OBJ_3D *ptb3d = &ptrobj->Obj;
// wheels rot
int32 c, d;
int32 x, y, z;
// Trick to avoid crushing the groups in AffOneObject().
ObjectSetInterFrame(ptb3d);
if (ptrobj->_workFlags.bIsFalling || ptrobj->_workFlags.bANIM_MASTER_GRAVITY) {
return;
}
LongRotate(0, ptb->SpeedInc * 1024, ptb3d->Beta);
ptrobj->_processActor.x = ptb3d->X + X0 / 1024;
ptrobj->_processActor.z = ptb3d->Z + Z0 / 1024;
// Ideal altitude
yw = CalculAltitudeObjet(ptrobj->_processActor.x, ptrobj->_processActor.z, -1); // Ground Y for XZ
// test altitude #2: Forbidden triangles
// Front left wheel direction
ptb3d->CurrentFrame[3].Beta = (int16)ptb->BetaWheel;
// Front right wheel direction
ptb3d->CurrentFrame[6].Beta = (int16)ptb->BetaWheel;
// Management of 4 separate wheels.
// front right wheel
LongRotate(-400, 400, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (260 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[0].X, ptb->Wheel[0].Y, ptb->Wheel[0].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[0].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[0].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[0].X = x;
ptb->Wheel[0].Y = y;
ptb->Wheel[0].Z = z;
// front left wheel
LongRotate(400, 400, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (260 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[1].X, ptb->Wheel[1].Y, ptb->Wheel[1].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[1].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[1].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[1].X = x;
ptb->Wheel[1].Y = y;
ptb->Wheel[1].Z = z;
// back left wheel
LongRotate(400, -350, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (360 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[2].X, ptb->Wheel[2].Y, ptb->Wheel[2].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[2].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[2].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[2].X = x;
ptb->Wheel[2].Y = y;
ptb->Wheel[2].Z = z;
// back right wheel
LongRotate(-400, -350, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (360 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[3].X, ptb->Wheel[3].Y, ptb->Wheel[3].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[3].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[3].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[3].X = x;
ptb->Wheel[3].Y = y;
ptb->Wheel[3].Z = z;
// front right wheel
ptb3d->CurrentFrame[4].Alpha = (int16)ptb->Wheel[1].Angle;
// front left wheel
ptb3d->CurrentFrame[7].Alpha = (int16)ptb->Wheel[0].Angle;
// back left wheel
ptb3d->CurrentFrame[11].Alpha = (int16)ptb->Wheel[2].Angle;
// back right wheel
ptb3d->CurrentFrame[9].Alpha = (int16)ptb->Wheel[3].Angle;
// Car inclination (pitch)
ptb3d->CurrentFrame[1].Type = 0;
LongRotate(0, 400, ptb3d->Beta);
x1 = X0;
z1 = Z0;
LongRotate(0, -400, ptb3d->Beta);
if (Nxw + x1 >= 0 && Nxw + x1 < 32768 && Nzw + z1 >= 0 && Nzw + z1 < 32768) {
y = CalculAltitudeObjet(Nxw + x1, Nzw + z1, -1);
} else {
y = yw;
}
if (Nxw + X0 >= 0 && Nxw + X0 < 32768 && Nzw + Z0 >= 0 && Nzw + Z0 < 32768) {
y1 = CalculAltitudeObjet(Nxw + X0, Nzw + Z0, -1);
} else {
y1 = yw;
}
ptb3d->CurrentFrame[1].Alpha = (int16)(1024 - GetAngle2D(0, y, 800, y1));
ptb->Alpha = ptb3d->CurrentFrame[1].Alpha;
// Car inclination (roll)
LongRotate(400, 0, ptb3d->Beta);
x1 = X0;
z1 = Z0;
LongRotate(-400, 0, ptb3d->Beta);
if (Nxw + X0 >= 0 && Nxw + X0 < 32768 && Nzw + Z0 >= 0 && Nzw + Z0 < 32768) {
y = CalculAltitudeObjet(Nxw + X0, Nzw + Z0, -1);
} else {
y = yw;
}
if (Nxw + x1 >= 0 && Nxw + x1 < 32768 && Nzw + z1 >= 0 && Nzw + z1 < 32768) {
y1 = CalculAltitudeObjet(Nxw + x1, Nzw + z1, -1);
} else {
y1 = yw;
}
ptb3d->CurrentFrame[1].Gamma = (int16)GetAngle2D(y, 0, y1, 800);
// Steering wheel
ptb3d->CurrentFrame[12].Gamma = (int16)-ptb->BetaWheel;
// Twinsen's head
ptb3d->CurrentFrame[14].Beta = (int16)ptb->BetaWheel;
#endif
}
void Buggy::moveBuggy(ActorStruct *ptrobj) {
#if 0
S_BUGGY *ptb = &ListBuggy[0];
T_OBJ_3D *ptb3d = &ptrobj->Obj;
int32 pitch = 0;
int32 flagattack = false;
int32 speedinc;
int32 rotlevel;
int32 timerhr, deltatimer;
timerhr = TimerRefHR;
deltatimer = timerhr - ptb->LastTimer;
if ((Input & I_THROW) && (PtrComportement->Flags & CF_WEAPON)) {
// Are we in mage?
if (TabInv[FLAG_TUNIQUE].IdObj3D == 0) {
_engine->_actor->initBody(BodyType::btTunicTir, OWN_ACTOR_SCENE_INDEX);
} else {
_engine->_actor->initBody(BodyType::btMageTir, OWN_ACTOR_SCENE_INDEX);
}
_engine->_animations->initAnim(AnimationTypes::kThrowBall, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
/* control direction pendant Aiming */
if (!ptrobj->_workFlags.bIsRotationByAnim) {
ptb3d->Beta += GetDeltaMove(&ptrobj->BoundAngle.Move);
ptb3d->Beta &= 4095;
_engine->_movements->initRealAngleConst(ptrobj);
}
_engine->_movements->_lastJoyFlag = true;
flagattack = true;
} else {
if (LastInput & I_THROW) {
// We finished shooting with the buggy,
// we close the hood
_engine->_actor->initBody(BodyType::btTunic, OWN_ACTOR_SCENE_INDEX);
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
}
}
if (!flagattack && !ptrobj->_workFlags.bIsFalling && !ptrobj->_workFlags.bANIM_MASTER_GRAVITY) {
_engine->_movements->clearRealAngle(ptrobj);
if (_engine->_movements->_lastJoyFlag && (((Input & I_JOY) != LastMyJoy) || ((Input & I_FIRE) != LastMyFire))) {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
Pushing = false;
}
_engine->_movements->_lastJoyFlag = false;
// Pushing contains the number of the object being pushed
// So 1000 is an impossible value used as an initialization flag
// no animation
if (Pushing == 1000) {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
Pushing = false;
}
if (Input & I_UP) {
if (Pushing) {
_engine->_animations->initAnim(AnimationTypes::kPush, ANIM_TEMPO, OWN_ACTOR_SCENE_INDEX);
_engine->_movements->_lastJoyFlag = true;
} else {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
_engine->_movements->_lastJoyFlag = true;
}
} else if (Input & I_DOWN) {
_engine->_animations->initAnim(AnimationTypes::kBackward, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
_engine->_movements->_lastJoyFlag = true;
}
}
if (!ptrobj->_workFlags.bIsFalling && !ptrobj->_workFlags.bANIM_MASTER_GRAVITY) {
// check speed command
if ((Input & I_UP) // accelerating
&& !flagattack) {
ptb->Speed += deltatimer * 4;
if (!TimerGear)
TimerGear = TimerRefHR + TEMPO_GEAR;
else {
if (Gear < 0)
Gear = 0;
if (TimerRefHR > TimerGear && Gear < 2) {
Gear++;
TimerGear = TimerRefHR + TEMPO_GEAR;
}
}
} else if ((Input & I_DOWN) // brake / reverse
&& !flagattack) {
ptb->Speed -= deltatimer * 12;
Gear = -1;
TimerGear = 0;
} else // slow down
{
if (ptb->Speed > 0) {
ptb->Speed -= deltatimer * 7;
if (ptb->Speed < 0) {
ptb->Speed = 0;
}
}
if (ptb->Speed < 0) {
ptb->Speed += deltatimer * 7;
if (ptb->Speed > 0) {
ptb->Speed = 0;
}
}
Gear = 0;
TimerGear = 0;
}
if (ptb->Speed < -2000)
ptb->Speed = -2000;
if (ptb->Speed > MAX_SPEED)
ptb->Speed = MAX_SPEED;
speedinc = ptb->Speed * deltatimer / 1000;
} else {
speedinc = 0;
}
// check dir
if (!flagattack) {
if (Input & I_RIGHT) {
ptb->BetaWheel = -300;
if (ptb->Speed) {
rotlevel = -ptb->SpeedRot * speedinc / ptb->Speed;
} else {
rotlevel = 0;
}
} else if (Input & I_LEFT) {
ptb->BetaWheel = 300;
if (ptb->Speed) {
rotlevel = ptb->SpeedRot * speedinc / ptb->Speed;
} else {
rotlevel = 0;
}
} else {
ptb->BetaWheel = 0;
rotlevel = 0;
}
if (ptrobj->_staticFlags.bSKATING) {
ptb->Speed = 3000;
speedinc = ptb->Speed * deltatimer / 1000;
} else {
if (ptb->Speed >= 0) {
ptb3d->Beta += rotlevel;
} else {
ptb3d->Beta -= rotlevel;
}
ptb3d->Beta = ClampAngle(ptb3d->Beta);
}
} else {
ptb->BetaWheel = 0;
}
LastMyJoy = Input & I_JOY;
LastMyFire = Input & I_FIRE;
LastInput = Input;
ptb->LastTimer = timerhr;
ptb->SpeedInc = speedinc;
if (ptrobj->SampleAlways && _engine->_sound->isSamplePlaying(ptrobj->SampleAlways)) {
int32 pitch;
switch (Gear) {
case -1:
pitch = boundRuleThree(3000, MAX_SAMPLE_PITCH2, MAX_SPEED, ABS(ptb->Speed));
break;
case 0:
pitch = boundRuleThree(3000, MAX_SAMPLE_PITCH, MAX_SPEED, ABS(ptb->Speed));
if (pitch >= MAX_SAMPLE_PITCH)
TimerGear = 1;
break;
case 1:
pitch = boundRuleThree(MAX_SAMPLE_PITCH2, MIN_SAMPLE_PITCH2, TEMPO_GEAR, TimerGear - TimerRefHR);
break;
default:
pitch = MAX_SAMPLE_PITCH2;
}
_engine->_sound->ChangePitchbendSample(ptrobj->SampleAlways, pitch);
}
#endif
}
} // namespace TwinE

View File

@@ -0,0 +1,81 @@
/* 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 TWINE_SCENE_BUGGY_H
#define TWINE_SCENE_BUGGY_H
#include "common/scummsys.h"
#include "twine/input.h"
#include "twine/scene/actor.h"
#define BUGGY_PRESENT 0x80
#define NUM_BUGGY ((uint8)(NumBuggy & ~(BUGGY_PRESENT)))
#define IsBuggyPresent() (NumBuggy & BUGGY_PRESENT)
namespace TwinE {
class Buggy {
private:
TwinEEngine *_engine;
int32 Gear = 0;
int32 TimerGear;
public:
#define MAX_BUGGYS 2
struct S_ONE_WHEEL {
int32 Angle = 0;
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
};
struct S_BUGGY {
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
int32 Cube = 0;
int32 Beta = 0;
int32 Alpha = 0;
int32 Gamma = 0;
S_ONE_WHEEL Wheel[4];
int32 BetaWheel = 0;
int32 SpeedInc = 0;
int32 SpeedRot = 0;
int32 Speed = 0;
int32 LastTimer = 0;
};
// TODO: rename and hide
S_BUGGY ListBuggy[MAX_BUGGYS];
uint8 NumBuggy;
Buggy(TwinEEngine *engine) : _engine(engine) {}
void initBuggy(uint8 numobj, uint32 flaginit);
void resetBuggy();
void takeBuggy();
void leaveBuggy(HeroBehaviourType behaviour);
void doAnimBuggy(ActorStruct *ptrobj);
void moveBuggy(ActorStruct *ptrobj);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,524 @@
/* 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 "twine/scene/collision.h"
#include "common/util.h"
#include "twine/debugger/debug_state.h"
#include "twine/renderer/renderer.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/extra.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
namespace TwinE {
Collision::Collision(TwinEEngine *engine) : _engine(engine) {
}
bool Collision::checkZvOnZv(int32 actorIdx1, int32 actorIdx2) const {
const ActorStruct *actor1 = _engine->_scene->getActor(actorIdx1);
const ActorStruct *actor2 = _engine->_scene->getActor(actorIdx2);
const IVec3 &processActor = actor1->_processActor;
const IVec3 &mins1 = processActor + actor1->_boundingBox.mins;
const IVec3 &maxs1 = processActor + actor1->_boundingBox.maxs;
const IVec3 &mins2 = actor2->posObj() + actor2->_boundingBox.mins;
const IVec3 &maxs2 = actor2->posObj() + actor2->_boundingBox.maxs;
if (mins1.x >= maxs2.x) {
return false;
}
if (maxs1.x <= mins2.x) {
return false;
}
if (mins1.y > (maxs2.y + 1)) {
return false;
}
if (mins1.y <= (maxs2.y - SIZE_BRICK_Y)) {
return false;
}
if (maxs1.y <= mins2.y) {
return false;
}
if (mins1.z >= maxs2.z) {
return false;
}
if (maxs1.z <= mins2.z) {
return false;
}
return true;
}
void Collision::reajustPos(IVec3 &processActor, ShapeType brickShape) const {
if (brickShape <= ShapeType::kSolid) {
return;
}
const int32 xw = (_collision.x * SIZE_BRICK_XZ) - DEMI_BRICK_XZ; // upper left corner of the brick
const int32 yw = _collision.y * SIZE_BRICK_Y;
const int32 zw = (_collision.z * SIZE_BRICK_XZ) - DEMI_BRICK_XZ;
// double-side stairs
switch (brickShape) {
case ShapeType::kDoubleSideStairsTop1:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsTopRight;
} else {
brickShape = ShapeType::kStairsTopLeft;
}
break;
case ShapeType::kDoubleSideStairsBottom1:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsBottomRight;
} else {
brickShape = ShapeType::kStairsBottomLeft;
}
break;
case ShapeType::kDoubleSideStairsTop2:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsTopLeft;
} else {
brickShape = ShapeType::kStairsTopRight;
}
break;
case ShapeType::kDoubleSideStairsBottom2:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsBottomLeft;
} else {
brickShape = ShapeType::kStairsBottomRight;
}
break;
case ShapeType::kDoubleSideStairsLeft1:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsBottomLeft;
} else {
brickShape = ShapeType::kStairsTopLeft;
}
break;
case ShapeType::kDoubleSideStairsRight1:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsBottomRight;
} else {
brickShape = ShapeType::kStairsTopRight;
}
break;
case ShapeType::kDoubleSideStairsLeft2:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsTopLeft;
} else {
brickShape = ShapeType::kStairsBottomLeft;
}
break;
case ShapeType::kDoubleSideStairsRight2:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsTopRight;
} else {
brickShape = ShapeType::kStairsBottomRight;
}
break;
default:
break;
}
switch (brickShape) {
case ShapeType::kStairsTopLeft:
processActor.y = yw + boundRuleThree(0, SIZE_BRICK_Y, SIZE_BRICK_XZ, processActor.x - xw);
break;
case ShapeType::kStairsTopRight:
processActor.y = yw + boundRuleThree(0, SIZE_BRICK_Y, SIZE_BRICK_XZ, processActor.z - zw);
break;
case ShapeType::kStairsBottomLeft:
processActor.y = yw + boundRuleThree(SIZE_BRICK_Y, 0, SIZE_BRICK_XZ, processActor.z - zw);
break;
case ShapeType::kStairsBottomRight:
processActor.y = yw + boundRuleThree(SIZE_BRICK_Y, 0, SIZE_BRICK_XZ, processActor.x - xw);
break;
default:
break;
}
}
void Collision::handlePushing(IVec3 &processActor, const IVec3 &minsTest, const IVec3 &maxsTest, ActorStruct *ptrobj, ActorStruct *ptrobjt) {
const int32 newAngle = _engine->_movements->getAngle(processActor, ptrobjt->posObj());
// protect against chain reactions
if (ptrobjt->_flags.bCanBePushed && !ptrobj->_flags.bCanBePushed) {
ptrobjt->_animStep.y = 0;
if (ptrobjt->_flags.bUseMiniZv) {
if (newAngle >= LBAAngles::ANGLE_45 && newAngle < LBAAngles::ANGLE_135 && ptrobj->_beta >= LBAAngles::ANGLE_45 && ptrobj->_beta < LBAAngles::ANGLE_135) {
ptrobjt->_animStep.x = SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
if (newAngle >= LBAAngles::ANGLE_135 && newAngle < LBAAngles::ANGLE_225 && ptrobj->_beta >= LBAAngles::ANGLE_135 && ptrobj->_beta < LBAAngles::ANGLE_225) {
ptrobjt->_animStep.z = -SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
if (newAngle >= LBAAngles::ANGLE_225 && newAngle < LBAAngles::ANGLE_315 && ptrobj->_beta >= LBAAngles::ANGLE_225 && ptrobj->_beta < LBAAngles::ANGLE_315) {
ptrobjt->_animStep.x = -SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
if ((newAngle >= LBAAngles::ANGLE_315 || newAngle < LBAAngles::ANGLE_45) && (ptrobj->_beta >= LBAAngles::ANGLE_315 || ptrobj->_beta < LBAAngles::ANGLE_45)) {
ptrobjt->_animStep.z = SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
} else {
// induced displacement before readjustment?
ptrobjt->_animStep.x = processActor.x - ptrobj->_oldPos.x;
ptrobjt->_animStep.z = processActor.z - ptrobj->_oldPos.z;
}
}
// so patch tempo
if ((ptrobjt->_boundingBox.maxs.x - ptrobjt->_boundingBox.mins.x == ptrobjt->_boundingBox.maxs.z - ptrobjt->_boundingBox.mins.z) &&
(ptrobj->_boundingBox.maxs.x - ptrobj->_boundingBox.mins.x == ptrobj->_boundingBox.maxs.z - ptrobj->_boundingBox.mins.z)) {
if (newAngle >= LBAAngles::ANGLE_45 && newAngle < LBAAngles::ANGLE_135) {
processActor.x = minsTest.x - ptrobj->_boundingBox.maxs.x;
}
if (newAngle >= LBAAngles::ANGLE_135 && newAngle < LBAAngles::ANGLE_225) {
processActor.z = maxsTest.z - ptrobj->_boundingBox.mins.z;
}
if (newAngle >= LBAAngles::ANGLE_225 && newAngle < LBAAngles::ANGLE_315) {
processActor.x = maxsTest.x - ptrobj->_boundingBox.mins.x;
}
if (newAngle >= LBAAngles::ANGLE_315 || newAngle < LBAAngles::ANGLE_45) {
processActor.z = minsTest.z - ptrobj->_boundingBox.maxs.z;
}
} else if (!ptrobj->_workFlags.bIsFalling) {
// refuse pos
processActor = ptrobj->_oldPos;
}
}
bool Collision::checkValidObjPos(int32 actorIdx) {
const ActorStruct *ptrobj = _engine->_scene->getActor(actorIdx);
const IVec3 m0 = ptrobj->posObj() + ptrobj->_boundingBox.mins;
const IVec3 m1 = ptrobj->posObj() + ptrobj->_boundingBox.maxs;
if (m0.x < 0 || m0.x > SCENE_SIZE_MAX) {
return false;
}
if (m1.x < 0 || m1.x > SCENE_SIZE_MAX) {
return false;
}
if (m0.z < 0 || m0.z > SCENE_SIZE_MAX) {
return false;
}
if (m1.z < 0 || m1.z > SCENE_SIZE_MAX) {
return false;
}
Grid *grid = _engine->_grid;
if (grid->worldColBrickFull(m0.x, m0.y, m0.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
if (grid->worldColBrickFull(m1.x, m0.y, m0.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
if (grid->worldColBrickFull(m1.x, m0.y, m1.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
if (grid->worldColBrickFull(m0.x, m0.y, m1.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
for (int32 n = 0; n < _engine->_scene->_nbObjets; ++n) {
const ActorStruct *ptrobjt = _engine->_scene->getActor(n);
if (n != actorIdx && ptrobjt->_body != -1 && !ptrobj->_flags.bIsInvisible && ptrobjt->_carryBy != actorIdx) {
const IVec3 &t0 = ptrobjt->posObj() + ptrobjt->_boundingBox.mins;
const IVec3 &t1 = ptrobjt->posObj() + ptrobjt->_boundingBox.maxs;
if (m0.x < t1.x && m1.x > t0.x && m0.y < t1.y && m1.y > t0.y && m0.z < t1.z && m1.z > t0.z) {
return false;
}
}
}
return true;
}
int32 Collision::checkObjCol(int32 actorIdx) {
ActorStruct *ptrobj = _engine->_scene->getActor(actorIdx);
IVec3 &processActor = ptrobj->_processActor;
IVec3 mins = processActor + ptrobj->_boundingBox.mins;
IVec3 maxs = processActor + ptrobj->_boundingBox.maxs;
int32 oldObjCol = ptrobj->_objCol;
ptrobj->_objCol = -1;
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
ActorStruct *ptrobjt = _engine->_scene->getActor(a);
// avoid current processed actor
if (a != actorIdx && ptrobjt->_body != -1 && !ptrobj->_flags.bIsInvisible && ptrobjt->_carryBy != actorIdx) {
const IVec3 &minsTest = ptrobjt->posObj() + ptrobjt->_boundingBox.mins;
const IVec3 &maxsTest = ptrobjt->posObj() + ptrobjt->_boundingBox.maxs;
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
ptrobj->_objCol = a; // mark as collision with actor a
if (a != oldObjCol) {
debugC(1, TwinE::kDebugCollision, "Actor %d is colliding with %d", actorIdx, a);
}
if (ptrobjt->_flags.bIsCarrierActor) {
if (ptrobj->_workFlags.bIsFalling) {
// I touch a carrier
processActor.y = maxsTest.y - ptrobj->_boundingBox.mins.y + 1;
ptrobj->_carryBy = a;
continue;
} else if (checkZvOnZv(actorIdx, a)) {
// I walk on a carrier
processActor.y = maxsTest.y - ptrobj->_boundingBox.mins.y + 1;
ptrobj->_carryBy = a;
continue;
}
} else {
// I step on someone
if (checkZvOnZv(actorIdx, a)) {
_engine->_actor->hitObj(actorIdx, a, 1, -1);
}
}
handlePushing(processActor, minsTest, maxsTest, ptrobj, ptrobjt);
}
}
}
// test moves ZV further if hit
if (ptrobj->_workFlags.bIsHitting) {
const IVec2 &destPos = _engine->_renderer->rotate(0, 200, ptrobj->_beta);
mins = processActor + ptrobj->_boundingBox.mins;
mins.x += destPos.x;
mins.z += destPos.y;
maxs = processActor + ptrobj->_boundingBox.maxs;
maxs.x += destPos.x;
maxs.z += destPos.y;
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
const ActorStruct *actorTest = _engine->_scene->getActor(a);
// avoid current processed actor
if (a != actorIdx && actorTest->_body != -1 && !actorTest->_flags.bIsInvisible && actorTest->_carryBy != actorIdx) {
const IVec3 minsTest = actorTest->posObj() + actorTest->_boundingBox.mins;
const IVec3 maxsTest = actorTest->posObj() + actorTest->_boundingBox.maxs;
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
_engine->_actor->hitObj(actorIdx, a, ptrobj->_hitForce, ptrobj->_beta + LBAAngles::ANGLE_180);
ptrobj->_workFlags.bIsHitting = 0;
}
}
}
}
return ptrobj->_objCol;
}
void Collision::setCollisionPos(const IVec3 &pos) {
_processCollision = pos;
}
uint32 Collision::doCornerReajustTwinkel(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask) {
IVec3 &processActor = actor->_processActor;
const IVec3 &oldPos = actor->_oldPos;
ShapeType orgcol = _engine->_grid->worldColBrick(processActor);
uint32 _col1 = 0;
processActor.x += x;
processActor.y += y;
processActor.z += z;
if (processActor.x >= 0 && processActor.z >= 0 && processActor.x <= SCENE_SIZE_MAX && processActor.z <= SCENE_SIZE_MAX) {
const BoundingBox &bbox = actor->_boundingBox;
reajustPos(processActor, orgcol);
ShapeType col = _engine->_grid->worldColBrickFull(processActor, bbox.maxs.y, OWN_ACTOR_SCENE_INDEX);
if (col == ShapeType::kSolid) {
_col1 |= damageMask;
if (_engine->_grid->worldColBrickFull(processActor.x, processActor.y, oldPos.z + z, bbox.maxs.y, OWN_ACTOR_SCENE_INDEX) == ShapeType::kSolid) {
if (_engine->_grid->worldColBrickFull(x + oldPos.x, processActor.y, processActor.z, bbox.maxs.y, OWN_ACTOR_SCENE_INDEX) != ShapeType::kSolid) {
_processCollision.x = oldPos.x;
}
} else {
_processCollision.z = oldPos.z;
}
}
}
processActor = _processCollision;
return _col1;
}
uint32 Collision::doCornerReajust(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask) {
IVec3 &processActor = actor->_processActor;
const IVec3 &previousActor = actor->_oldPos;
ShapeType orgcol = _engine->_grid->worldColBrick(processActor);
uint32 _col1 = 0;
processActor.x += x;
processActor.y += y;
processActor.z += z;
if (processActor.x >= 0 && processActor.z >= 0 && processActor.x <= SCENE_SIZE_MAX && processActor.z <= SCENE_SIZE_MAX) {
reajustPos(processActor, orgcol);
ShapeType col = _engine->_grid->worldColBrick(processActor);
if (col == ShapeType::kSolid) {
_col1 |= damageMask;
if (_engine->_grid->worldColBrick(processActor.x, processActor.y, previousActor.z + z) == ShapeType::kSolid) {
if (_engine->_grid->worldColBrick(x + previousActor.x, processActor.y, processActor.z) != ShapeType::kSolid) {
_processCollision.x = previousActor.x;
}
} else {
_processCollision.z = previousActor.z;
}
}
}
processActor = _processCollision;
return _col1;
}
void Collision::receptionObj(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (IS_HERO(actorIdx)) {
const IVec3 &processActor = actor->_processActor;
const int32 fall = _engine->_scene->_startYFalling - processActor.y;
if (fall >= SIZE_BRICK_Y * 8) {
const IVec3 &actorPos = actor->posObj();
_engine->_extra->initSpecial(actorPos.x, actorPos.y + 1000, actorPos.z, ExtraSpecialType::kHitStars);
if (fall >= SIZE_BRICK_Y * 16) {
actor->setLife(0);
} else {
actor->addLife(-1);
}
_engine->_animations->initAnim(AnimationTypes::kLandingHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
} else if (fall > 2 * SIZE_BRICK_Y) {
_engine->_animations->initAnim(AnimationTypes::kLanding, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
} else {
if (actor->_workFlags.bWasWalkingBeforeFalling) {
// try to not interrupt walk animation if Twinsen falls down from small height
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kStanding, actorIdx);
} else {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kStanding, actorIdx);
}
}
_engine->_scene->_startYFalling = 0;
} else {
_engine->_animations->initAnim(AnimationTypes::kLanding, AnimType::kAnimationAllThen, actor->_nextGenAnim, actorIdx);
}
if (actor->_workFlags.bIsFalling) {
debugC(1, TwinE::kDebugCollision, "Actor %d reset falling", actorIdx);
}
actor->_workFlags.bIsFalling = 0;
actor->_workFlags.bWasWalkingBeforeFalling = 0;
}
int32 Collision::extraCheckObjCol(ExtraListStruct *extra, int32 actorIdx) {
const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(extra->sprite);
const IVec3 mins = bbox->mins + extra->pos;
const IVec3 maxs = bbox->maxs + extra->pos;
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
const ActorStruct *actorTest = _engine->_scene->getActor(a);
if (a != actorIdx && actorTest->_body != -1) {
const IVec3 minsTest = actorTest->posObj() + actorTest->_boundingBox.mins;
const IVec3 maxsTest = actorTest->posObj() + actorTest->_boundingBox.maxs;
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
if (extra->strengthOfHit != 0) {
_engine->_actor->hitObj(actorIdx, a, extra->strengthOfHit, -1);
}
return a;
}
}
}
return -1;
}
bool Collision::fullWorldColBrick(int32 x, int32 y, int32 z, const IVec3 &oldPos) {
if (_engine->_grid->worldColBrick(oldPos) != ShapeType::kNone) {
return true;
}
const int32 averageX = ABS(x + oldPos.x) / 2;
const int32 averageY = ABS(y + oldPos.y) / 2;
const int32 averageZ = ABS(z + oldPos.z) / 2;
if (_engine->_grid->worldColBrick(averageX, averageY, averageZ) != ShapeType::kNone) {
return true;
}
if (_engine->_grid->worldColBrick(ABS(oldPos.x + averageX) / 2, ABS(oldPos.y + averageY) / 2, ABS(oldPos.z + averageZ) / 2) != ShapeType::kNone) {
return true;
}
if (_engine->_grid->worldColBrick(ABS(x + averageX) / 2, ABS(y + averageY) / 2, ABS(z + averageZ) / 2) != ShapeType::kNone) {
return true;
}
return false;
}
int32 Collision::extraCheckExtraCol(ExtraListStruct *extra, int32 extraIdx) const {
int32 index = extra->sprite;
const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(index);
const IVec3 mins = bbox->mins + extra->pos;
const IVec3 maxs = bbox->maxs + extra->pos;
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
const ExtraListStruct *extraTest = &_engine->_extra->_extraList[i];
if (i != extraIdx && extraTest->sprite != -1) {
const BoundingBox *testbbox = _engine->_resources->_spriteBoundingBox.bbox(++index);
const IVec3 minsTest = testbbox->mins + extraTest->pos;
const IVec3 maxsTest = testbbox->maxs + extraTest->pos;
if (mins.x >= minsTest.x) {
continue;
}
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
return i;
}
}
}
return -1;
}
void Collision::doImpact(int32 num, int32 x, int32 y, int32 z, int32 owner) {
debugC(3, kDebugLevels::kDebugCollision, "Collision::doImpact(%i, %i, %i, %i, %i)", num, x, y, z, owner);
// TODO: Implement me
}
} // namespace TwinE

View 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 TWINE_SCENE_COLLISION_H
#define TWINE_SCENE_COLLISION_H
#include "common/scummsys.h"
#include "twine/shared.h"
namespace TwinE {
class ActorStruct;
struct ExtraListStruct;
class TwinEEngine;
class Collision {
private:
TwinEEngine *_engine;
void handlePushing(IVec3 &processActor, const IVec3 &minsTest, const IVec3 &maxsTest, ActorStruct *actor, ActorStruct *actorTest);
/** Actor collision coordinate */
IVec3 _processCollision; // SaveNxw, SaveNyw, SaveNzw
public:
Collision(TwinEEngine *engine);
/** Actor collision coordinate */
IVec3 _collision; // YMap
/**
* Check if actor 1 is standing in actor 2
* @param actorIdx1 Actor 1 index
* @param actorIdx2 Actor 2 index
*/
bool checkZvOnZv(int32 actorIdx1, int32 actorIdx2) const;
void doImpact(int32 num, int32 x, int32 y, int32 z, int32 owner);
/**
* Reajust actor position in scene according with brick shape bellow actor
* @param brickShape Shape of brick bellow the actor
*/
void reajustPos(IVec3 &processActor, ShapeType brickShape) const;
/**
* Check collision with actors
* @param actorIx Current process actor index
*/
int32 checkObjCol(int32 actorIdx);
bool checkValidObjPos(int32 actorIdx);
void setCollisionPos(const IVec3 &pos);
/**
* Check Hero collision with bricks
* @param x Hero X coordinate
* @param y Hero Y coordinate
* @param z Hero Z coordinate
* @param damageMask Cause damage mask
*/
uint32 doCornerReajustTwinkel(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask);
/**
* Check other actor collision with bricks
* @param x Actor X coordinate
* @param y Actor Y coordinate
* @param z Actor Z coordinate
* @param damageMask Cause damage mask
*/
uint32 doCornerReajust(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask);
/** Make actor to stop falling */
void receptionObj(int actorIdx);
/**
* Check extra collision with actors
* @param extra to process
* @param actorIdx actor to check collision
*/
int32 extraCheckObjCol(ExtraListStruct *extra, int32 actorIdx);
/** Check extra collision with bricks */
bool fullWorldColBrick(int32 x, int32 y, int32 z, const IVec3 &oldPos);
/**
* Check extra collision with another extra
* @param extra to process
* @param extraIdx extra index to check collision
*/
int32 extraCheckExtraCol(ExtraListStruct *extra, int32 extraIdx) const;
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,149 @@
/* 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 "twine/scene/dart.h"
#include "twine/audio/sound.h"
#include "twine/renderer/redraw.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/scene.h"
namespace TwinE {
void Dart::InitDarts() {
int32 x0, x1, y0, y1, z0, z1;
#if 0
uint8 *ptrbody;
T_BODY_HEADER *ptr;
ptrbody = (uint8 *)GivePtrObjFix(BODY_3D_DART);
if (!ptrbody) {
char tmpFilePath[ADELINE_MAX_PATH];
GetResPath(tmpFilePath, ADELINE_MAX_PATH, OBJFIX_HQR_NAME);
TheEndCheckFile(tmpFilePath);
}
// Calcule ZV des flechettes
ptr = (T_BODY_HEADER *)ptrbody;
x0 = ptr->XMin;
x1 = ptr->XMax;
y0 = ptr->YMin;
y1 = ptr->YMax;
z0 = ptr->ZMin;
z1 = ptr->ZMax;
#else
x0 = x1 = y0 = y1 = z0 = z1 = 0;
#endif
// Average
int32 size = ((x1 - x0) + (z1 - z0)) / 4;
T_DART *ptrd = ListDart;
for (uint32 t = 0; t < MAX_DARTS; t++, ptrd++) {
ptrd->Body = BODY_3D_DART;
ptrd->XMin = -size;
ptrd->XMax = size;
ptrd->YMin = y0;
ptrd->YMax = y1;
ptrd->ZMin = -size;
ptrd->ZMax = size;
ptrd->Flags = 0;
ptrd->NumCube = -1;
}
}
int32 Dart::GetDart() {
T_DART *ptrd;
int32 t;
ptrd = ListDart;
for (t = 0; t < MAX_DARTS; t++, ptrd++) {
if (ptrd->Flags & DART_TAKEN) {
return t;
}
}
return -1;
}
void Dart::TakeAllDarts() {
T_DART *ptrd;
int32 n;
ptrd = ListDart;
for (n = 0; n < MAX_DARTS; n++, ptrd++) {
ptrd->Flags |= DART_TAKEN;
}
_engine->_gameState->setDarts(MAX_DARTS);
}
void Dart::CheckDartCol(ActorStruct *ptrobj) {
int32 n;
T_DART *ptrd;
int32 x0, y0, z0, x1, y1, z1;
int32 xt0, yt0, zt0, xt1, yt1, zt1;
if (ptrobj->_flags.bIsInvisible)
return;
x0 = ptrobj->_posObj.x + ptrobj->_boundingBox.mins.x;
x1 = ptrobj->_posObj.x + ptrobj->_boundingBox.maxs.x;
y0 = ptrobj->_posObj.y + ptrobj->_boundingBox.mins.y;
y1 = ptrobj->_posObj.y + ptrobj->_boundingBox.maxs.y;
z0 = ptrobj->_posObj.z + ptrobj->_boundingBox.mins.z;
z1 = ptrobj->_posObj.z + ptrobj->_boundingBox.maxs.z;
ptrd = ListDart;
for (n = 0; n < MAX_DARTS; n++, ptrd++) {
if (ptrd->NumCube == _engine->_scene->_numCube && !(ptrd->Flags & DART_TAKEN)) {
xt0 = ptrd->PosX + ptrd->XMin;
xt1 = ptrd->PosX + ptrd->XMax;
yt0 = ptrd->PosY + ptrd->YMin;
yt1 = ptrd->PosY + ptrd->YMax;
zt0 = ptrd->PosZ + ptrd->ZMin;
zt1 = ptrd->PosZ + ptrd->ZMax;
if (x0 < xt1 && x1 > xt0 && y0 < yt1 && y1 > yt0 && z0 < zt1 && z1 > zt0) {
ptrd->Flags |= DART_TAKEN;
_engine->_gameState->addDart();
#if 0
_engine->_sound->playSample(SAMPLE_BONUS_TROUVE, 0x1000, 0, 1,
ptrd->PosX, ptrd->PosY, ptrd->PosZ);
_engine->_redraw->addOverlay(OverlayType::koSprite | INCRUST_YCLIP,
SPRITE_DART,
15, 30,
0, 0, 2);
#endif
}
}
}
}
} // namespace TwinE

View File

@@ -0,0 +1,69 @@
/* 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 TWINE_SCENE_DART_H
#define TWINE_SCENE_DART_H
#include "twine/scene/actor.h"
#include "twine/twine.h"
#define MAX_DARTS 3
#define BODY_3D_DART 61
// dart flags
#define DART_TAKEN (1 << 0)
namespace TwinE {
class Dart {
private:
TwinEEngine *_engine;
public:
struct T_DART {
int32 PosX = 0;
int32 PosY = 0;
int32 PosZ = 0;
int32 Alpha = 0;
int32 Beta = 0;
int32 Body = 0;
int32 NumCube = 0; // Number of the cube in which the dart is located
uint32 Flags = 0u;
int32 XMin = 0; // ZV of the darts
int32 YMin = 0;
int32 ZMin = 0;
int32 XMax = 0;
int32 YMax = 0;
int32 ZMax = 0;
};
T_DART ListDart[MAX_DARTS];
Dart(TwinEEngine *engine) : _engine(engine) {}
void InitDarts();
int32 GetDart();
void TakeAllDarts();
void CheckDartCol(ActorStruct *ptrobj);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,824 @@
/* 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 "twine/scene/extra.h"
#include "common/util.h"
#include "twine/audio/sound.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/collision.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
namespace TwinE {
/** Hit Stars shape info */
static const ShapeData hitStarsData[]{
{4, -6},
{19, -6},
{7, 2},
{12, 16},
{0, 7},
{-12, 16},
{-7, 2},
{-19, -6},
{-4, -6}};
/** Explode Cloud shape info */
static const ShapeData explodeCloudData[]{
{0, -20},
{6, -16},
{8, -10},
{14, -12},
{20, -4},
{18, 4},
{12, 4},
{16, 8},
{8, 16},
{2, 12},
{-4, 18},
{-10, 16},
{-12, 8},
{-16, 10},
{-20, 4},
{-12, -8},
{-6, -6},
{-10, -12}};
const ExtraShape hitStarsShape { ARRAYSIZE(hitStarsData), hitStarsData };
const ExtraShape explodeCloudShape { ARRAYSIZE(explodeCloudData), explodeCloudData };
Extra::Extra(TwinEEngine *engine) : _engine(engine) {}
int32 Extra::extraSearch(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActor, int32 maxSpeed, int32 strengthOfHit) { // ExtraSearch
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::SEARCH_OBJ;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.actorIdx = actorIdx;
extra->spawnTime = targetActor;
extra->destPos.z = maxSpeed;
extra->strengthOfHit = strengthOfHit;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, maxSpeed, LBAAngles::ANGLE_17, &extra->trackActorMove);
const ActorStruct *actor = _engine->_scene->getActor(targetActor);
extra->angle = _engine->_movements->getAngle(extra->pos, actor->posObj());
return i;
}
return -1;
}
int32 Extra::addExtraExplode(int32 x, int32 y, int32 z) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = SPRITEHQR_EXPLOSION_FIRST_FRAME;
extra->type = ExtraType::TIME_OUT | ExtraType::EXPLOSION;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.lifeTime = 40;
extra->spawnTime = _engine->timerRef;
extra->strengthOfHit = 0;
return i;
}
return -1;
}
void Extra::clearExtra() {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
extra->sprite = -1;
extra->info1 = 1;
}
}
void Extra::initFly(ExtraListStruct *extra, int32 xAngle, int32 yAngle, int32 x, int32 extraAngle) {
extra->type |= ExtraType::FLY;
extra->lastPos = extra->pos;
IVec2 destPos = _engine->_renderer->rotate(x, 0, xAngle);
extra->destPos.y = -destPos.y;
destPos = _engine->_renderer->rotate(0, destPos.x, yAngle);
extra->destPos.x = destPos.x;
extra->destPos.z = destPos.y;
extra->angle = extraAngle;
extra->spawnTime = _engine->timerRef;
}
int32 Extra::initSpecial(int32 x, int32 y, int32 z, ExtraSpecialType type) {
const int16 flag = EXTRA_SPECIAL_MASK + (int16)type;
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = flag;
extra->info1 = 0;
if (type == ExtraSpecialType::kHitStars) {
extra->type = ExtraType::TIME_OUT | ExtraType::END_COL;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
initFly(extra, _engine->getRandomNumber(LBAAngles::ANGLE_90) + LBAAngles::ANGLE_45, _engine->getRandomNumber(LBAAngles::ANGLE_360), 50, 20);
extra->strengthOfHit = 0;
extra->payload.lifeTime = 100;
} else if (type == ExtraSpecialType::kExplodeCloud) {
extra->type = ExtraType::TIME_OUT;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->strengthOfHit = 0;
extra->spawnTime = _engine->timerRef;
extra->payload.lifeTime = 5;
}
return i;
}
return -1;
}
int Extra::getBonusSprite(BonusParameter bonusParameter) const {
int numBonus = 0;
int8 bonusSprites[5];
if (bonusParameter.kashes) {
bonusSprites[numBonus++] = SPRITEHQR_KASHES;
}
if (bonusParameter.lifepoints) {
bonusSprites[numBonus++] = SPRITEHQR_LIFEPOINTS;
}
if (bonusParameter.magicpoints) {
bonusSprites[numBonus++] = SPRITEHQR_MAGICPOINTS;
}
if (bonusParameter.key) {
bonusSprites[numBonus++] = SPRITEHQR_KEY;
}
if (bonusParameter.cloverleaf) {
bonusSprites[numBonus++] = SPRITEHQR_CLOVERLEAF;
}
if (numBonus == 0) {
return -1;
}
const int bonusIndex = _engine->getRandomNumber(numBonus);
assert(bonusIndex >= 0);
assert(bonusIndex < numBonus);
int8 bonusSprite = bonusSprites[bonusIndex];
// if bonus is magic and no magic level yet, then give life points
if (!_engine->_gameState->_magicLevelIdx && bonusSprite == SPRITEHQR_MAGICPOINTS) {
bonusSprite = SPRITEHQR_LIFEPOINTS;
}
return bonusSprite;
}
int32 Extra::addExtraBonus(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 type, int32 bonusAmount) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = type;
extra->type = ExtraType::STOP_COL | ExtraType::TAKABLE | ExtraType::WAIT_SOME_TIME;
if (type != SPRITEHQR_KEY) {
extra->type |= ExtraType::TIME_OUT | ExtraType::FLASH;
}
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
initFly(extra, xAngle, yAngle, 40, ToAngle(15));
extra->strengthOfHit = 0;
extra->payload.lifeTime = _engine->toSeconds(20);
extra->info1 = bonusAmount;
return i;
}
return -1;
}
int32 Extra::throwExtra(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle, int32 strengthOfHit) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::END_OBJ | ExtraType::END_COL | ExtraType::IMPACT | ExtraType::WAIT_NO_COL;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
initFly(extra, xAngle, yAngle, xRotPoint, extraAngle);
extra->strengthOfHit = strengthOfHit;
extra->spawnTime = _engine->timerRef;
extra->payload.actorIdx = actorIdx;
extra->info1 = 0;
return i;
}
return -1;
}
int32 Extra::addExtraAiming(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActorIdx, int32 finalAngle, int32 strengthOfHit) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::SEARCH_OBJ;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.actorIdx = actorIdx;
extra->spawnTime = targetActorIdx;
extra->destPos.z = finalAngle;
extra->strengthOfHit = strengthOfHit;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, finalAngle, LBAAngles::ANGLE_17, &extra->trackActorMove);
const ActorStruct *actor = _engine->_scene->getActor(targetActorIdx);
extra->angle = _engine->_movements->getAngle(extra->pos, actor->posObj());
return i;
}
return -1;
}
int32 Extra::searchBonusKey() const {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
const ExtraListStruct *extra = &_extraList[i];
if (extra->sprite == SPRITEHQR_KEY) {
return i;
}
}
return -1;
}
int32 Extra::extraSearchKey(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 extraIdx) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::MAGIC_BALL_KEY;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.extraIdx = extraIdx;
extra->destPos.z = 4000;
extra->strengthOfHit = 0;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, 4000, LBAAngles::ANGLE_17, &extra->trackActorMove);
extra->angle = _engine->_movements->getAngle(extra->pos, _extraList[extraIdx].pos);
return i;
}
return -1;
}
void Extra::addExtraThrowMagicball(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle) {
int32 ballSprite = -1;
int32 ballStrength = 0;
switch (_engine->_gameState->_magicLevelIdx) {
case 0:
case 1:
ballSprite = SPRITEHQR_MAGICBALL_YELLOW;
ballStrength = 4;
break;
case 2:
ballSprite = SPRITEHQR_MAGICBALL_GREEN;
ballStrength = 6;
break;
case 3:
ballSprite = SPRITEHQR_MAGICBALL_RED;
ballStrength = 8;
break;
case 4:
ballSprite = SPRITEHQR_MAGICBALL_FIRE;
ballStrength = 10;
break;
}
_engine->_gameState->_magicBallType = ((_engine->_gameState->_magicPoint - 1) / 20) + 1;
if (_engine->_gameState->_magicPoint == 0) {
_engine->_gameState->_magicBallType = 0;
}
const int32 extraIdx = searchBonusKey();
if (extraIdx != -1) { // there is a key to aim
_engine->_gameState->_magicBallType = 5;
}
switch (_engine->_gameState->_magicBallType) {
case 0:
_engine->_gameState->_magicBall = throwExtra(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, xAngle, yAngle, xRotPoint, extraAngle, ballStrength);
break;
case 1:
_engine->_gameState->_magicBallCount = 4;
_engine->_gameState->_magicBall = throwExtra(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, xAngle, yAngle, xRotPoint, extraAngle, ballStrength);
break;
case 2:
case 3:
case 4:
_engine->_gameState->_magicBallType = 1;
_engine->_gameState->_magicBallCount = 4;
_engine->_gameState->_magicBall = throwExtra(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, xAngle, yAngle, xRotPoint, extraAngle, ballStrength);
break;
case 5:
assert(extraIdx != -1);
_engine->_gameState->_magicBall = extraSearchKey(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, extraIdx);
break;
}
if (_engine->_gameState->_magicPoint > 0) {
_engine->_gameState->_magicPoint--;
}
}
void Extra::aff2DShape(const ExtraShape &shapeTable, int32 x, int32 y, int32 color, int32 angle, int32 zoom, Common::Rect &renderRect) {
int shapeDataIndex = 0;
int16 shapeX = shapeTable.data[shapeDataIndex].x * zoom / 16;
int16 shapeZ = shapeTable.data[shapeDataIndex].z * zoom / 16;
++shapeDataIndex;
_engine->clearScreenMinMax(renderRect);
IVec2 destPos = _engine->_renderer->rotate(shapeX, shapeZ, angle);
const int32 computedX = destPos.x + x;
const int32 computedY = destPos.y + y;
_engine->adjustScreenMax(renderRect, computedX, computedY);
int32 currentX = computedX;
int32 currentY = computedY;
for (int32 numEntries = 1; numEntries < shapeTable.n; ++numEntries) {
shapeX = shapeTable.data[shapeDataIndex].x * zoom / 16;
shapeZ = shapeTable.data[shapeDataIndex].z * zoom / 16;
++shapeDataIndex;
const int32 oldComputedX = currentX;
const int32 oldComputedY = currentY;
destPos = _engine->_renderer->rotate(shapeX, shapeZ, angle);
currentX = destPos.x + x;
currentY = destPos.y + y;
_engine->adjustScreenMax(renderRect, currentX, currentY);
_engine->_interface->drawLine(oldComputedX, oldComputedY, currentX, currentY, color);
}
_engine->_interface->drawLine(currentX, currentY, computedX, computedY, color);
}
void Extra::affSpecial(int32 extraIdx, int32 x, int32 y, Common::Rect &renderRect) {
ExtraListStruct *extra = &_extraList[extraIdx];
ExtraSpecialType specialType = (ExtraSpecialType)(extra->sprite & (EXTRA_SPECIAL_MASK - 1));
switch (specialType) {
case ExtraSpecialType::kHitStars:
aff2DShape(hitStarsShape, x, y, COLOR_WHITE, (_engine->timerRef * 32) & LBAAngles::ANGLE_270, 4, renderRect);
break;
case ExtraSpecialType::kExplodeCloud: {
int32 zoom = 1 + _engine->timerRef - extra->spawnTime;
if (zoom > 32) {
zoom = 32;
}
aff2DShape(explodeCloudShape, x, y, COLOR_WHITE, LBAAngles::ANGLE_0, zoom, renderRect);
break;
}
case ExtraSpecialType::kFountain:
break;
}
}
void Extra::bounceExtra(ExtraListStruct *extra, int32 x, int32 y, int32 z) {
if (_engine->_grid->worldColBrick(x, extra->pos.y, z) != ShapeType::kNone) {
extra->destPos.y = -extra->destPos.y;
}
if (_engine->_grid->worldColBrick(extra->pos.x, y, z) != ShapeType::kNone) {
extra->destPos.x = -extra->destPos.x;
}
if (_engine->_grid->worldColBrick(x, y, extra->pos.z) != ShapeType::kNone) {
extra->destPos.z = -extra->destPos.z;
}
extra->pos.x = x;
extra->lastPos.x = x;
extra->pos.y = y;
extra->lastPos.y = y;
extra->pos.z = z;
extra->lastPos.z = z;
extra->spawnTime = _engine->timerRef;
}
void Extra::gereExtras() {
int32 currentExtraX = 0;
int32 currentExtraY = 0;
int32 currentExtraZ = 0;
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite == -1) {
continue;
}
// process extra life time
if (extra->type & ExtraType::TIME_OUT) {
if (extra->payload.lifeTime + extra->spawnTime <= _engine->timerRef) {
extra->sprite = -1;
continue;
}
}
// reset extra
if (extra->type & ExtraType::ONE_FRAME) {
extra->sprite = -1;
continue;
}
const int32 deltaT = _engine->timerRef - extra->spawnTime;
if (extra->type & ExtraType::EXPLOSION) {
extra->sprite = boundRuleThree(SPRITEHQR_EXPLOSION_FIRST_FRAME, 100, 30, deltaT);
continue;
}
// process extra moving
if (extra->type & ExtraType::FLY) {
currentExtraX = extra->pos.x;
currentExtraY = extra->pos.y;
currentExtraZ = extra->pos.z;
const int32 currentExtraSpeedX = extra->destPos.x * deltaT;
extra->pos.x = currentExtraSpeedX + extra->lastPos.x;
const int32 currentExtraSpeedY = extra->destPos.y * deltaT;
extra->pos.y = currentExtraSpeedY + extra->lastPos.y - ABS(extra->angle * deltaT * deltaT / 16);
extra->pos.z = extra->destPos.z * deltaT + extra->lastPos.z;
// check if extra is out of scene
if (extra->pos.y < 0 || extra->pos.x < 0 || extra->pos.x > SCENE_SIZE_MAX || extra->pos.z < 0 || extra->pos.z > SCENE_SIZE_MAX) {
// if extra is Magic Ball
if (i == _engine->_gameState->_magicBall) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z,
spriteIdx, OWN_ACTOR_SCENE_INDEX, 10000, 0);
}
// if can take extra on ground
if (extra->type & ExtraType::TAKABLE) {
extra->type &= ~(ExtraType::FLY | ExtraType::STOP_COL);
} else {
extra->sprite = -1;
}
continue;
}
}
if (extra->type & ExtraType::WAIT_SOME_TIME) {
if (_engine->timerRef - extra->spawnTime > 40) {
extra->type &= ~ExtraType::WAIT_SOME_TIME;
}
continue;
}
// process actor target hit
if (extra->type & ExtraType::SEARCH_OBJ) {
int32 actorIdxAttacked = extra->spawnTime;
int32 actorIdx = extra->payload.actorIdx;
const ActorStruct *actor = _engine->_scene->getActor(actorIdxAttacked);
currentExtraX = actor->_posObj.x;
currentExtraY = actor->_posObj.y + 1000;
currentExtraZ = actor->_posObj.z;
const int32 tmpAngle = _engine->_movements->getAngle(extra->pos, actor->posObj());
const int32 angle = ClampAngle(tmpAngle - extra->angle);
if (angle > LBAAngles::ANGLE_140 && angle < LBAAngles::ANGLE_210) {
if (extra->strengthOfHit) {
_engine->_actor->hitObj(actorIdx, actorIdxAttacked, extra->strengthOfHit, -1);
}
if (i == _engine->_gameState->_magicBall) {
_engine->_gameState->_magicBall = -1;
}
extra->sprite = -1;
continue;
}
const int32 angle2 = _engine->_movements->getAngle(extra->pos.y, 0, currentExtraY, _engine->_movements->_targetActorDistance);
int32 pos = extra->trackActorMove.getRealValueFromTime(_engine->timerRef);
if (!pos) {
pos = 1;
}
IVec2 destPos = _engine->_renderer->rotate(pos, 0, angle2);
extra->pos.y -= destPos.y;
destPos = _engine->_renderer->rotate(0, destPos.x, tmpAngle);
extra->pos.x += destPos.x;
extra->pos.z += destPos.y;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, extra->destPos.z, LBAAngles::ANGLE_17, &extra->trackActorMove);
if (actorIdxAttacked == _engine->_collision->extraCheckObjCol(extra, actorIdx)) {
if (i == _engine->_gameState->_magicBall) {
_engine->_gameState->_magicBall = -1;
}
extra->sprite = -1;
continue;
}
}
// process magic ball extra aiming for key
if (extra->type & ExtraType::MAGIC_BALL_KEY) {
ExtraListStruct *extraKey = &_extraList[extra->payload.extraIdx];
const int32 extraIdx = extra->payload.extraIdx;
const int32 tmpAngle = _engine->_movements->getAngle(extra->pos, extraKey->pos);
const int32 angle = ClampAngle(tmpAngle - extra->angle);
if (angle > LBAAngles::ANGLE_140 && angle < LBAAngles::ANGLE_210) {
_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
if (extraKey->info1 > 1) {
const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
_engine->_redraw->addOverlay(OverlayType::koNumber, extraKey->info1, projPos.x, projPos.y, COLOR_BLACK, OverlayPosType::koNormal, 2);
}
_engine->_redraw->addOverlay(OverlayType::koSprite, SPRITEHQR_KEY, 10, 30, 0, OverlayPosType::koNormal, 2);
_engine->_gameState->addKeys(extraKey->info1);
extraKey->sprite = -1;
extra->sprite = -1;
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, SPRITEHQR_KEY, 0, 8000, 0);
continue;
}
const int32 angle2 = _engine->_movements->getAngle(extra->pos.y, 0, extraKey->pos.y, _engine->_movements->_targetActorDistance);
int32 pos = extra->trackActorMove.getRealValueFromTime(_engine->timerRef);
if (!pos) {
pos = 1;
}
IVec2 destPos = _engine->_renderer->rotate(pos, 0, angle2);
extra->pos.y -= destPos.y;
destPos = _engine->_renderer->rotate(0, destPos.x, tmpAngle);
extra->pos.x += destPos.x;
extra->pos.z += destPos.y;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, extra->destPos.z, LBAAngles::ANGLE_17, &extra->trackActorMove);
if (extraIdx == _engine->_collision->extraCheckExtraCol(extra, _engine->_gameState->_magicBall)) {
_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
if (extraKey->info1 > 1) {
const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
_engine->_redraw->addOverlay(OverlayType::koNumber, extraKey->info1, projPos.x, projPos.y, COLOR_BLACK, OverlayPosType::koNormal, 2);
}
_engine->_redraw->addOverlay(OverlayType::koSprite, SPRITEHQR_KEY, 10, 30, 0, OverlayPosType::koNormal, 2);
_engine->_gameState->addKeys(extraKey->info1);
extraKey->sprite = -1;
extra->sprite = -1;
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, SPRITEHQR_KEY, 0, 8000, 0);
continue;
}
if (extraKey->sprite == -1) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
extra->sprite = -1;
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z,
spriteIdx, 0, 8000, 0);
continue;
}
}
// process extra collision with actors
if (extra->type & ExtraType::END_OBJ) {
if (_engine->_collision->extraCheckObjCol(extra, extra->payload.actorIdx) != -1) {
// if extra is Magic Ball
if (i == _engine->_gameState->_magicBall) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z,
spriteIdx, 0, 10000, 0);
}
extra->sprite = -1;
continue;
}
}
// process extra collision with scene ground
if (extra->type & ExtraType::END_COL) {
bool flagcol = false;
if (_engine->_collision->fullWorldColBrick(currentExtraX, currentExtraY, currentExtraZ, extra->pos)) {
// if not touch the ground
if (!(extra->type & ExtraType::WAIT_NO_COL)) {
flagcol = true;
}
} else {
// if touch the ground
if (extra->type & ExtraType::WAIT_NO_COL) {
extra->type &= ~ExtraType::WAIT_NO_COL; // set flag out of ground
}
}
if (flagcol) {
// show explode cloud
if (extra->type & ExtraType::IMPACT) {
initSpecial(currentExtraX, currentExtraY, currentExtraZ, ExtraSpecialType::kExplodeCloud);
}
// if extra is magic ball
if (i == _engine->_gameState->_magicBall) {
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(300) - 150;
_engine->_sound->mixSample3D(Samples::Hit, pitchBend, 1, extra->pos, -1);
// can't bounce with not magic points
if (_engine->_gameState->_magicBallType <= 0) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, spriteIdx, 0, 10000, 0);
extra->sprite = -1;
continue;
}
// if has magic points
if (_engine->_gameState->_magicBallType == 1) {
if (!_engine->_gameState->_magicBallCount--) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, spriteIdx, 0, 10000, 0);
extra->sprite = -1;
continue;
}
bounceExtra(extra, currentExtraX, currentExtraY, currentExtraZ);
}
} else {
extra->sprite = -1;
continue;
}
}
}
// extra stop moving while collision with bricks
if (extra->type & ExtraType::STOP_COL) {
bool process = false;
if (_engine->_collision->fullWorldColBrick(currentExtraX, currentExtraY, currentExtraZ, extra->pos)) {
// if not touch the ground
if (!(extra->type & ExtraType::WAIT_NO_COL)) {
process = true;
}
} else {
// if touch the ground
if (extra->type & ExtraType::WAIT_NO_COL) {
extra->type &= ~ExtraType::WAIT_NO_COL; // set flag out of ground
}
}
if (process) {
const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(extra->sprite);
extra->pos.y = (_engine->_collision->_collision.y * SIZE_BRICK_Y) + SIZE_BRICK_Y - bbox->mins.y;
extra->type &= ~(ExtraType::STOP_COL | ExtraType::FLY);
continue;
}
}
// get extras on ground
if ((extra->type & ExtraType::TAKABLE) && !(extra->type & ExtraType::FLY)) {
// if hero touch extra
if (_engine->_collision->extraCheckObjCol(extra, -1) == 0) {
_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, extra->pos, -1);
if (extra->info1 > 1) {
const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
const int16 fontColor = COLOR_158;
_engine->_redraw->addOverlay(OverlayType::koNumber, extra->info1, projPos.x, projPos.y, fontColor, OverlayPosType::koNormal, 2);
}
_engine->_redraw->addOverlay(OverlayType::koSprite, extra->sprite, 10, 30, 0, OverlayPosType::koNormal, 2);
if (extra->sprite == SPRITEHQR_KASHES) {
_engine->_gameState->addKashes(extra->info1);
} else if (extra->sprite == SPRITEHQR_LIFEPOINTS) {
_engine->_scene->_sceneHero->addLife(extra->info1);
} else if (extra->sprite == SPRITEHQR_MAGICPOINTS && _engine->_gameState->_magicLevelIdx) {
_engine->_gameState->addMagicPoints(extra->info1 * 2);
} else if (extra->sprite == SPRITEHQR_KEY) {
_engine->_gameState->addKeys(extra->info1);
} else if (extra->sprite == SPRITEHQR_CLOVERLEAF) {
_engine->_gameState->addLeafs(extra->info1);
}
extra->sprite = -1;
}
}
}
}
} // namespace TwinE

139
engines/twine/scene/extra.h Normal file
View 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 TWINE_SCENE_EXTRA_H
#define TWINE_SCENE_EXTRA_H
#include "common/scummsys.h"
#include "common/rect.h"
#include "twine/scene/actor.h"
namespace TwinE {
#define EXTRA_MAX_ENTRIES 50
#define EXTRA_SPECIAL_MASK 0x8000
struct ShapeData {
int16 x;
int16 z;
};
struct ExtraShape {
int n;
const ShapeData *data;
};
enum ExtraType {
TIME_OUT = 1 << 0, // 0x0001
FLY = 1 << 1, // 0x0002
END_OBJ = 1 << 2, // 0x0004
END_COL = 1 << 3, // 0x0008
STOP_COL = 1 << 4, // 0x0010
TAKABLE = 1 << 5, // 0x0020
FLASH = 1 << 6, // 0x0040
SEARCH_OBJ = 1 << 7, // 0x0080
IMPACT = 1 << 8, // 0x0100
MAGIC_BALL_KEY = 1 << 9, // 0x0200
TIME_IN = 1 << 10, // 0x0400
ONE_FRAME = 1 << 11, // 0x0800
EXPLOSION = 1 << 12, // 0x1000 EXTRA_EXPLO
WAIT_NO_COL = 1 << 13, // 0x2000 EXTRA_WAIT_NO_COL
WAIT_SOME_TIME = 1 << 14, // 0x4000
COMPUTE_TRAJ = 1 << 15 // 0x8000 used in dotemu enhanced to render the magic ball trajectories
};
struct ExtraListStruct {
int16 sprite = 0; /**< a value of -1 indicates that this instance is free to use */
IVec3 pos;
IVec3 lastPos;
IVec3 destPos;
RealValue trackActorMove;
uint16 type = 0; /**< ExtraType bitmask */
int16 angle = 0; // weight
int32 spawnTime = 0; // memo timer 50hz
union payload { // field_ 1C
int16 lifeTime;
int16 actorIdx;
int16 extraIdx;
int16 unknown;
} payload{0};
int16 strengthOfHit = 0; // apply damage if != 0
int16 info1 = 0; // various - number for zone giver
};
class TwinEEngine;
class Extra {
private:
TwinEEngine *_engine;
void initFly(ExtraListStruct *extra, int32 xAngle, int32 yAngle, int32 x, int32 extraAngle);
void bounceExtra(ExtraListStruct *extra, int32 x, int32 y, int32 z);
int32 searchBonusKey() const;
int32 extraSearchKey(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 extraIdx);
void aff2DShape(const ExtraShape &shapeTable, int32 x, int32 y, int32 color, int32 angle, int32 zoom, Common::Rect &renderRect);
public:
Extra(TwinEEngine *engine);
ExtraListStruct _extraList[EXTRA_MAX_ENTRIES];
int32 extraSearch(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActor, int32 maxSpeed, int32 strengthOfHit);
/**
* Add extra explosion
* @param x Explosion X coordinate
* @param y Explosion Y coordinate
* @param z Explosion Z coordinate
*/
int32 addExtraExplode(int32 x, int32 y, int32 z);
inline int32 extraExplo(const IVec3 &pos) {
return addExtraExplode(pos.x, pos.y, pos.z);
}
/** Reset all used extras */
void clearExtra();
int32 initSpecial(int32 x, int32 y, int32 z, ExtraSpecialType type);
int32 addExtraBonus(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 type, int32 bonusAmount);
inline int32 addExtraBonus(const IVec3 &pos, int32 xAngle, int32 yAngle, int32 type, int32 bonusAmount) {
return addExtraBonus(pos.x, pos.y, pos.z, xAngle, yAngle, type, bonusAmount);
}
int32 throwExtra(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle, int32 strengthOfHit);
int32 addExtraAiming(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActorIdx, int32 finalAngle, int32 strengthOfHit);
void addExtraThrowMagicball(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle);
void affSpecial(int32 extraIdx, int32 x, int32 y, Common::Rect &renderRect);
int getBonusSprite(BonusParameter bonusParameter) const;
/** Process extras */
void gereExtras();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,681 @@
/* 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 "twine/scene/gamestate.h"
#include "common/rect.h"
#include "common/str.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/menu/menu.h"
#include "twine/menu/menuoptions.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/extra.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
#include "twine/twine.h"
#define SIZE_FOUND_OBJ 130
namespace TwinE {
GameState::GameState(TwinEEngine *engine) : _engine(engine) {
clearGameFlags();
Common::fill(&_inventoryFlags[0], &_inventoryFlags[NUM_INVENTORY_ITEMS], 0);
Common::fill(&_holomapFlags[0], &_holomapFlags[MAX_HOLO_POS_2], 0);
Common::fill(&_gameListChoice[0], &_gameListChoice[10], TextId::kNone);
}
void GameState::init3DGame() {
_engine->_renderer->setIsoProjection(_engine->width() / 2 - 8 - 1, _engine->height() / 2, SIZE_BRICK_XZ);
_engine->_renderer->setPosCamera(0, 0, 0);
_engine->_renderer->setAngleCamera(LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0);
_engine->_renderer->setLightVector(_engine->_scene->_alphaLight, _engine->_scene->_betaLight, LBAAngles::ANGLE_0);
}
void GameState::initGameStateVars() {
debug(2, "Init game state variables");
_engine->_extra->clearExtra();
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
_engine->_redraw->overlayList[i].num = -1;
}
for (int32 i = 0; i < ARRAYSIZE(_engine->_scene->_listFlagCube); i++) {
_engine->_scene->_listFlagCube[i] = 0;
}
clearGameFlags();
Common::fill(&_inventoryFlags[0], &_inventoryFlags[NUM_INVENTORY_ITEMS], 0);
_engine->_scene->initSceneVars();
Common::fill(&_holomapFlags[0], &_holomapFlags[MAX_HOLO_POS_2], 0);
}
void GameState::initHeroVars() {
_engine->_actor->initObject(OWN_ACTOR_SCENE_INDEX); // reset Hero
_magicBall = -1;
_inventoryNumLeafsBox = 2;
_inventoryNumLeafs = 2;
_goldPieces = 0;
_nbLittleKeys = 0;
_magicPoint = 0;
_weapon = false;
_engine->_scene->_sceneHero->_genBody = BodyType::btNormal;
_engine->_scene->_sceneHero->setLife(_engine->getMaxLife());
_engine->_scene->_sceneHero->_talkColor = COLOR_BRIGHT_BLUE;
}
void GameState::initEngineVars() {
debug(2, "Init engine variables");
_engine->_interface->unsetClip();
_engine->_scene->_alphaLight = LBAAngles::ANGLE_315;
_engine->_scene->_betaLight = LBAAngles::ANGLE_334;
init3DGame();
initGameStateVars();
initHeroVars();
_engine->_scene->_sceneStart.x = 16 * SIZE_BRICK_XZ;
_engine->_scene->_sceneStart.y = 24 * SIZE_BRICK_Y;
_engine->_scene->_sceneStart.z = 16 * SIZE_BRICK_XZ;
_engine->_scene->_numCube = SCENE_CEILING_GRID_FADE_1;
_engine->_scene->_newCube = LBA1SceneId::Citadel_Island_Prison;
_engine->_sceneLoopState = SceneLoopState::Continue;
_engine->_scene->_mecaPenguinIdx = -1;
_engine->_menuOptions->flagCredits = false;
_inventoryNumLeafs = 0;
_inventoryNumLeafsBox = 2;
_magicPoint = 0;
_goldPieces = 0;
_nbLittleKeys = 0;
_inventoryNumGas = 0;
_engine->_actor->_cropBottomScreen = 0;
_magicLevelIdx = 0;
_weapon = false;
setChapter(0);
_engine->_scene->_sceneTextBank = TextBankId::Options_and_menus;
_engine->_scene->_numObjFollow = OWN_ACTOR_SCENE_INDEX;
_engine->_actor->_heroBehaviour = HeroBehaviourType::kNormal;
_engine->_actor->_previousHeroAngle = 0;
_engine->_actor->_previousHeroBehaviour = HeroBehaviourType::kNormal;
}
// https://web.archive.org/web/*/http://lbafileinfo.kazekr.net/index.php?title=LBA1:Savegame
bool GameState::loadGame(Common::SeekableReadStream *file) {
if (file == nullptr) {
return false;
}
if (!_engine->isLBA1()) {
warning("Loading not implemented for lba2");
return false;
}
debug(2, "Load game");
const byte saveFileVersion = file->readByte();
// 4 is dotemu enhanced version of lba1
if (saveFileVersion != 3 && saveFileVersion != 4) {
warning("Could not load savegame - wrong magic byte");
return false;
}
initEngineVars();
int playerNameIdx = 0;
do {
const byte c = file->readByte();
_engine->_menuOptions->_saveGameName[playerNameIdx++] = c;
if (c == '\0') {
break;
}
if (playerNameIdx >= ARRAYSIZE(_engine->_menuOptions->_saveGameName)) {
warning("Failed to load savegame. Invalid playername.");
return false;
}
} while (true);
byte numGameFlags = file->readByte();
if (numGameFlags != NUM_GAME_FLAGS_LBA1) {
warning("Failed to load gameflags. Expected %u, but got %u", NUM_GAME_FLAGS_LBA1, numGameFlags);
return false;
}
for (uint8 i = 0; i < numGameFlags; ++i) {
setGameFlag(i, file->readByte());
}
_engine->_scene->_newCube = file->readByte(); // scene index
setChapter(file->readByte());
_engine->_actor->_heroBehaviour = (HeroBehaviourType)file->readByte();
_engine->_actor->_previousHeroBehaviour = _engine->_actor->_heroBehaviour;
_engine->_scene->_sceneHero->setLife(file->readByte());
setKashes(file->readSint16LE());
_magicLevelIdx = file->readByte();
setMagicPoints(file->readByte());
setLeafBoxes(file->readByte());
_engine->_scene->_sceneStart.x = file->readSint16LE();
_engine->_scene->_sceneStart.y = file->readSint16LE();
_engine->_scene->_sceneStart.z = file->readSint16LE();
_engine->_scene->_sceneHero->_beta = ToAngle(file->readSint16LE());
_engine->_actor->_previousHeroAngle = _engine->_scene->_sceneHero->_beta;
_engine->_scene->_sceneHero->_genBody = (BodyType)file->readByte();
const byte numHolomapFlags = file->readByte(); // number of holomap locations
if (numHolomapFlags != _engine->numHoloPos()) {
warning("Failed to load holomapflags. Got %u, expected %i", numHolomapFlags, _engine->numHoloPos());
return false;
}
file->read(_holomapFlags, _engine->numHoloPos());
setGas(file->readByte());
const byte numInventoryFlags = file->readByte(); // number of used inventory items, always 28
if (numInventoryFlags != NUM_INVENTORY_ITEMS) {
warning("Failed to load inventoryFlags. Got %u, expected %i", numInventoryFlags, NUM_INVENTORY_ITEMS);
return false;
}
file->read(_inventoryFlags, NUM_INVENTORY_ITEMS);
setLeafs(file->readByte());
_weapon = file->readByte();
if (saveFileVersion == 4) {
// the time the game was played
file->readUint32LE();
file->readUint32LE();
}
_engine->_scene->_numCube = SCENE_CEILING_GRID_FADE_1;
_engine->_scene->_flagChgCube = ScenePositionType::kReborn;
return true;
}
bool GameState::saveGame(Common::WriteStream *file) {
debug(2, "Save game");
if (!_engine->isLBA1()) {
warning("Saving not implemented for lba2");
return false;
}
if (_engine->_menuOptions->_saveGameName[0] == '\0') {
Common::strlcpy(_engine->_menuOptions->_saveGameName, "TwinEngineSave", sizeof(_engine->_menuOptions->_saveGameName));
}
int32 sceneIdx = _engine->_scene->_numCube;
if (sceneIdx == Polar_Island_end_scene || sceneIdx == Citadel_Island_end_sequence_1 || sceneIdx == Citadel_Island_end_sequence_2 || sceneIdx == Credits_List_Sequence) {
/* inventoryMagicPoints = 0x50 */
/* herobehaviour = 0 */
/* newheropos.x = 0xffff */
sceneIdx = Polar_Island_Final_Battle;
}
file->writeByte(0x03);
file->writeString(_engine->_menuOptions->_saveGameName);
file->writeByte('\0');
file->writeByte(NUM_GAME_FLAGS_LBA1);
for (uint8 i = 0; i < NUM_GAME_FLAGS_LBA1; ++i) {
file->writeByte((uint8)hasGameFlag(i));
}
file->writeByte(sceneIdx);
file->writeByte(getChapter());
file->writeByte((byte)_engine->_actor->_heroBehaviour);
file->writeByte(_engine->_scene->_sceneHero->_lifePoint);
file->writeSint16LE(_goldPieces);
file->writeByte(_magicLevelIdx);
file->writeByte(_magicPoint);
file->writeByte(_inventoryNumLeafsBox);
// we don't save the whole scene state - so we have to make sure that the hero is
// respawned at the start of the scene - and not at its current position
file->writeSint16LE(_engine->_scene->_sceneStart.x);
file->writeSint16LE(_engine->_scene->_sceneStart.y);
file->writeSint16LE(_engine->_scene->_sceneStart.z);
file->writeSint16LE(FromAngle(_engine->_scene->_sceneHero->_beta));
file->writeByte((uint8)_engine->_scene->_sceneHero->_genBody);
// number of holomap locations
file->writeByte(_engine->numHoloPos());
file->write(_holomapFlags, _engine->numHoloPos());
file->writeByte(_inventoryNumGas);
// number of inventory items
file->writeByte(NUM_INVENTORY_ITEMS);
file->write(_inventoryFlags, NUM_INVENTORY_ITEMS);
file->writeByte(_inventoryNumLeafs);
file->writeByte(_weapon ? 1 : 0);
file->writeByte(0);
return true;
}
void GameState::setChapter(int16 chapter) {
if (_engine->isLBA1()) {
_gameChapter = chapter;
return;
}
setGameFlag(253, chapter);
}
int16 GameState::getChapter() const {
if (_engine->isLBA1()) {
return _gameChapter;
}
return _listFlagGame[253];
}
void GameState::setGameFlag(uint8 index, int16 value) {
if (_listFlagGame[index] == value) {
return;
}
debug(2, "Set gameStateFlags[%u]=%u", index, value);
_listFlagGame[index] = value;
if (!value) {
return;
}
if ((index == GAMEFLAG_VIDEO_BAFFE || index == GAMEFLAG_VIDEO_BAFFE2 || index == GAMEFLAG_VIDEO_BAFFE3 || index == GAMEFLAG_VIDEO_BAFFE5) &&
_listFlagGame[GAMEFLAG_VIDEO_BAFFE] != 0 && _listFlagGame[GAMEFLAG_VIDEO_BAFFE2] != 0 && _listFlagGame[GAMEFLAG_VIDEO_BAFFE3] != 0 && _listFlagGame[GAMEFLAG_VIDEO_BAFFE5] != 0) {
// all 4 slap videos
_engine->unlockAchievement("LBA_ACH_012");
} else if (index == GAMEFLAG_VIDEO_BATEAU2) {
// second video of ferry trip
_engine->unlockAchievement("LBA_ACH_010");
} else if (index == (uint8)InventoryItems::kiUseSabre) {
_engine->unlockAchievement("LBA_ACH_002");
} else if (index == (uint8)InventoryItems::kBottleOfSyrup) {
_engine->unlockAchievement("LBA_ACH_007");
}
}
void GameState::doFoundObj(InventoryItems item) {
_engine->_grid->centerOnActor(_engine->_scene->_sceneHero);
// Hide hero in scene
_engine->_scene->_sceneHero->_flags.bIsInvisible = 1;
_engine->_redraw->drawScene(true);
_engine->_scene->_sceneHero->_flags.bIsInvisible = 0;
_engine->saveFrontBuffer();
IVec3 itemCamera;
itemCamera.x = _engine->_grid->_startCube.x * SIZE_BRICK_XZ;
itemCamera.y = _engine->_grid->_startCube.y * SIZE_BRICK_Y;
itemCamera.z = _engine->_grid->_startCube.z * SIZE_BRICK_XZ;
BodyData &bodyData = _engine->_scene->_sceneHero->_entityDataPtr->getBody(_engine->_scene->_sceneHero->_body);
const IVec3 bodyPos = _engine->_scene->_sceneHero->_posObj - itemCamera;
Common::Rect modelRect;
_engine->_renderer->renderIsoModel(bodyPos, LBAAngles::ANGLE_0, LBAAngles::ANGLE_45, LBAAngles::ANGLE_0, bodyData, modelRect);
_engine->_interface->setClip(modelRect);
const int32 itemX = (_engine->_scene->_sceneHero->_posObj.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
int32 itemY = _engine->_scene->_sceneHero->_posObj.y / SIZE_BRICK_Y;
if (_engine->_scene->_sceneHero->brickShape() != ShapeType::kNone) {
itemY++;
}
const int32 itemZ = (_engine->_scene->_sceneHero->_posObj.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick(itemX, itemY, itemZ);
IVec3 projPos = _engine->_renderer->projectPoint(bodyPos);
projPos.y -= 150;
const int32 boxTopLeftX = projPos.x - (SIZE_FOUND_OBJ / 2);
const int32 boxTopLeftY = projPos.y - (SIZE_FOUND_OBJ / 2);
const int32 boxBottomRightX = projPos.x + (SIZE_FOUND_OBJ / 2);
const int32 boxBottomRightY = projPos.y + (SIZE_FOUND_OBJ / 2);
const Common::Rect boxRect(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY);
_engine->_sound->mixSample(Samples::BigItemFound, 0x1000, 1, 128, 128);
// process vox play
_engine->_music->stopMusic();
_engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);
_engine->_interface->unsetClip();
_engine->_text->initItemFoundText(item);
_engine->_text->initDialWindow();
ProgressiveTextState textState = ProgressiveTextState::ContinueRunning;
_engine->_text->initVoxToPlayTextId((TextId)item);
const int32 bodyAnimIdx = _engine->_animations->searchAnim(AnimationTypes::kFoundItem, OWN_ACTOR_SCENE_INDEX);
const AnimData &ptranim = _engine->_resources->_animData[bodyAnimIdx];
_engine->_animations->stockInterAnim(bodyData, &bodyData._animTimerData);
uint frameanim = 0;
_engine->_redraw->_nbOptPhysBox = 0;
AnimTimerDataStruct animTimerData;
ScopedKeyMap uiKeyMap(_engine, uiKeyMapId);
int16 itemAngle = LBAAngles::ANGLE_0;
for (;;) {
FrameMarker frame(_engine, 66);
_engine->_interface->unsetClip();
_engine->_redraw->_nbPhysBox = 0;
_engine->_redraw->clsBoxes();
_engine->_interface->shadeBox(boxRect, 4);
_engine->_interface->setClip(boxRect);
itemAngle += LBAAngles::ANGLE_2;
_engine->_renderer->draw3dObject(projPos.x, projPos.y, _engine->_resources->_inventoryTable[item], itemAngle, 10000);
_engine->_menu->drawRectBorders(boxRect);
_engine->_redraw->addPhysBox(boxRect);
_engine->_interface->unsetClip();
init3DGame();
if (_engine->_animations->setInterAnimObjet(frameanim, ptranim, bodyData, &animTimerData)) {
frameanim++; // keyframe
if (frameanim >= ptranim.getNbFramesAnim()) {
frameanim = ptranim.getLoopFrame();
}
}
_engine->_renderer->renderIsoModel(bodyPos, LBAAngles::ANGLE_0, LBAAngles::ANGLE_45, LBAAngles::ANGLE_0, bodyData, modelRect);
_engine->_interface->setClip(modelRect);
_engine->_grid->drawOverBrick(itemX, itemY, itemZ);
_engine->_redraw->addPhysBox(modelRect);
if (textState == ProgressiveTextState::ContinueRunning) {
_engine->_interface->unsetClip();
textState = _engine->_text->nextDialChar();
} else {
_engine->_text->fadeInRemainingChars();
}
_engine->_redraw->flipBoxes();
_engine->readKeys();
if (_engine->_input->toggleAbortAction()) {
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
break;
}
if (_engine->_input->toggleActionIfActive(TwinEActionType::UINextPage)) {
if (textState == ProgressiveTextState::End) {
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
break;
}
if (textState == ProgressiveTextState::NextPage) {
textState = ProgressiveTextState::ContinueRunning;
}
}
_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry);
// advance the timer to play animations
_engine->timerRef++;
debugC(3, kDebugLevels::kDebugTimers, "FoundObj time: %i", _engine->timerRef);
}
while (_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry)) {
FrameMarker frame(_engine);
_engine->readKeys();
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
break;
}
}
init3DGame();
_engine->_text->initSceneTextBank();
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
}
void GameState::gameAskChoice(TextId choiceIdx) {
_engine->saveFrontBuffer();
_gameChoicesSettings.reset();
_gameChoicesSettings.setTextBankId((TextBankId)((int)_engine->_scene->_sceneTextBank + (int)TextBankId::Citadel_Island));
// filled via script
for (int32 i = 0; i < _gameNbChoices; i++) {
_gameChoicesSettings.addButton(_gameListChoice[i], 0);
}
_engine->_text->drawAskQuestion(choiceIdx);
_engine->_menu->doGameMenu(&_gameChoicesSettings);
const int16 activeButton = _gameChoicesSettings.getActiveButton();
_gameChoice = _gameListChoice[activeButton];
// get right VOX entry index
if (_engine->_text->initVoxToPlayTextId(_gameChoice)) {
while (_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry)) {
FrameMarker frame(_engine);
if (_engine->shouldQuit()) {
break;
}
}
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
_engine->_text->_hasHiddenVox = false;
_engine->_text->_voxHiddenIndex = 0;
}
}
void GameState::processGameoverAnimation() {
const int32 tmpLbaTime = _engine->timerRef;
_engine->testRestoreModeSVGA(false);
// workaround to fix hero redraw after drowning
_engine->_scene->_sceneHero->_flags.bIsInvisible = 1;
_engine->_redraw->drawScene(true);
_engine->_scene->_sceneHero->_flags.bIsInvisible = 0;
// TODO: inSceneryView
_engine->setPalette(_engine->_screens->_ptrPal);
_engine->saveFrontBuffer();
BodyData gameOverPtr;
if (!gameOverPtr.loadFromHQR(Resources::HQR_RESS_FILE, RESSHQR_GAMEOVERMDL, _engine->isLBA1())) {
return;
}
_engine->_sound->stopSamples();
_engine->_music->stopMusicMidi(); // stop fade music
_engine->_renderer->setProjection(_engine->width() / 2, _engine->height() / 2, 128, 200, 200);
int32 startLbaTime = _engine->timerRef;
const Common::Rect &rect = _engine->centerOnScreen(_engine->width() / 2, _engine->height() / 2);
_engine->_interface->setClip(rect);
int32 zoom = 50000;
Common::Rect dummy;
while (!_engine->_input->toggleAbortAction() && (_engine->timerRef - startLbaTime) <= _engine->toSeconds(10)) {
FrameMarker frame(_engine, 66);
_engine->readKeys();
if (_engine->shouldQuit()) {
return;
}
zoom = boundRuleThree(40000, 3200, _engine->toSeconds(10), _engine->timerRef - startLbaTime);
const int32 angle = ruleThree32(1, LBAAngles::ANGLE_360, _engine->toSeconds(2), (_engine->timerRef - startLbaTime) % _engine->toSeconds(2));
_engine->blitWorkToFront(rect);
_engine->_renderer->setFollowCamera(0, 0, 0, 0, -angle, 0, zoom);
_engine->_renderer->affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, gameOverPtr, dummy);
_engine->timerRef++;
debugC(3, kDebugLevels::kDebugTimers, "GameOver time: %i", _engine->timerRef);
}
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(2000) - (2000 / 2);
_engine->_sound->mixSample(Samples::Explode, pitchBend, 1, 128, 128);
_engine->blitWorkToFront(rect);
_engine->_renderer->setFollowCamera(0, 0, 0, 0, 0, 0, zoom);
_engine->_renderer->affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, gameOverPtr, dummy);
_engine->delaySkip(2000);
_engine->_interface->unsetClip();
_engine->restoreFrontBuffer();
init3DGame();
_engine->timerRef = tmpLbaTime;
}
void GameState::giveUp() {
_engine->_sound->stopSamples();
// TODO: is an autosave desired on giving up? I don't think so. What did the original game do here?
//_engine->autoSave();
initGameStateVars();
_engine->_scene->stopRunningGame();
}
int16 GameState::setGas(int16 value) {
_inventoryNumGas = CLIP<int16>(value, 0, 100);
return _inventoryNumGas;
}
void GameState::addGas(int16 value) {
setGas(_inventoryNumGas + value);
}
// late game items from lba1 classic new game +
void GameState::handleLateGameItems() {
if (!_endGameItems) {
return;
}
debug("Give end game items");
_endGameItems = false;
_magicLevelIdx = 4;
setMaxMagicPoints();
giveItem(InventoryItems::kiUseSabre);
giveItem(InventoryItems::kiProtoPack);
giveItem(InventoryItems::kiHolomap);
giveItem(InventoryItems::kiTunic);
giveItem(InventoryItems::kiMagicBall);
giveItem(InventoryItems::kSendellsMedallion);
giveItem(InventoryItems::kiPenguin);
giveItem(InventoryItems::kGasItem);
giveItem(InventoryItems::kiCloverLeaf);
addGas(10);
}
int16 GameState::setKashes(int16 value) {
_goldPieces = CLIP<int16>(value, 0, 999);
if (_engine->_gameState->_goldPieces >= 500) {
_engine->unlockAchievement("LBA_ACH_011");
}
return _goldPieces;
}
int16 GameState::setZlitos(int16 value) {
_zlitosPieces = CLIP<int16>(value, 0, 999);
return _zlitosPieces;
}
int16 GameState::setKeys(int16 value) {
_nbLittleKeys = MAX<int16>(0, value);
return _nbLittleKeys;
}
void GameState::addKeys(int16 val) {
setKeys(_nbLittleKeys + val);
}
void GameState::addKashes(int16 val) {
setKashes(_goldPieces + val);
}
int16 GameState::setMagicPoints(int16 val) {
_magicPoint = val;
if (_magicPoint > _magicLevelIdx * 20) {
_magicPoint = _magicLevelIdx * 20;
} else if (_magicPoint < 0) {
_magicPoint = 0;
}
return _magicPoint;
}
int16 GameState::setMaxMagicPoints() {
_magicPoint = _magicLevelIdx * 20;
return _magicPoint;
}
void GameState::addMagicPoints(int16 val) {
setMagicPoints(_magicPoint + val);
}
int16 GameState::setLeafs(int16 val) {
_inventoryNumLeafs = val;
if (_inventoryNumLeafs > _inventoryNumLeafsBox) {
_inventoryNumLeafs = _inventoryNumLeafsBox;
}
return _inventoryNumLeafs;
}
void GameState::addLeafs(int16 val) {
setLeafs(_inventoryNumLeafs + val);
}
int16 GameState::setLeafBoxes(int16 val) {
_inventoryNumLeafsBox = val;
if (_inventoryNumLeafsBox > 10) {
_inventoryNumLeafsBox = 10;
}
if (_inventoryNumLeafsBox == 5) {
_engine->unlockAchievement("LBA_ACH_003");
}
return _inventoryNumLeafsBox;
}
void GameState::addLeafBoxes(int16 val) {
setLeafBoxes(_inventoryNumLeafsBox + val);
}
void GameState::clearGameFlags() {
debug(2, "Clear all gameStateFlags");
Common::fill(&_listFlagGame[0], &_listFlagGame[NUM_GAME_FLAGS], 0);
}
int16 GameState::hasGameFlag(uint8 index) const {
debug(6, "Query gameStateFlags[%u]=%u", index, _listFlagGame[index]);
return _listFlagGame[index];
}
} // namespace TwinE

View 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/>.
*
*/
#ifndef TWINE_SCENE_GAMESTATE_H
#define TWINE_SCENE_GAMESTATE_H
#include "common/scummsys.h"
#include "twine/menu/menu.h"
#include "twine/shared.h"
namespace TwinE {
/** Magicball strength*/
enum MagicballStrengthType {
kNoBallStrength = 2,
kYellowBallStrength = 3,
kGreenBallStrength = 4,
kRedBallStrength = 6,
kFireBallStrength = 8
};
class TwinEEngine;
class GameState {
private:
TwinEEngine *_engine;
void initGameStateVars();
void initHeroVars();
MenuSettings _gameChoicesSettings;
/**
* LBA engine game flags to save quest states
*
* 0-27: inventory related
* 28-158: story related
* 159..199: unused
* 200-219: video related
* 220..255: unused
*
* 35: If 0, a zommed sequence of opening the ventilation shaft will be played when Twinsen escapes
* his house after arresting Zoe. Set to 1 after the sequence (also if Twinsen is killed during the arrest).
* 47: Value of 1 indicates that Twinsen has opened the door to the Citadel Island Tavern's basement.
* The door will be always open from now on.
* 70: Set to 1 if inventory items are taken from Twinsen when he goes to jail (inventory is empty),
* set to 0 after he gets back his stuff.
* 92: Set to 1 if the green grobo in the Citadel Island Tavern has told Twinsen about taking Zoe to the
* port and leaving for another island.
* 107: Set to 1 after Twinsen kills yellow groboclone in the Citadel Island Tavern (after the Tavern has
* been closed down). Makes the Tavern open again and groboclone not appear any more.
*/
int16 _listFlagGame[NUM_GAME_FLAGS]; // ListVarGame
// only lba1 - lba2 uses 253 gameflag
int16 _gameChapter = 0;
public:
GameState(TwinEEngine *engine);
/**
* LBA engine chapter
* 0: Imprisoned
* 1: Escape from the citadel
* 2: Zoe got captured
* 3: - looking for a young girl
* 4: - looking for a "friend"
* 5: The legend of Sendell
* 6: The book of Bu
* 7: Pirate LeBorne
* 8: - "good day"
* 9: - "good day"
* 10: - ?? nothing
* 11: - ?? nothing
* 12: - ?? nothing
* 13: - looking for plans
* 14: - still looking for plans
* 15: The final showdown - "good day"
*/
void setChapter(int16 chapter);
int16 getChapter() const;
/** Magic ball type index */
int16 _magicBall = 0;
/** Magic ball num bounce */
int16 _magicBallType = 0;
/** Magic ball auxiliar bounce number */
int16 _magicBallCount = 0; // magicBallParam
/** Magic level index */
int16 _magicLevelIdx = 0;
/** Store the number of inventory keys */
int16 _nbLittleKeys = 0;
/** Store the number of inventory kashes */
int16 _goldPieces = 0;
int16 _zlitosPieces = 0;
/** Store the number of inventory clover leafs boxes */
int16 _inventoryNumLeafsBox = 0;
/** Store the number of inventory clover leafs */
int16 _inventoryNumLeafs = 0;
/** Store the number of inventory magic points */
int16 _magicPoint = 0;
/** Store the number of gas */
int16 _inventoryNumGas = 0;
/** Its using FunFrock Sabre */
bool _weapon = false;
bool _endGameItems = false;
/**
* Inventory used flags
* 0 means never used, 1 means already used and automatic re-use
*/
uint8 _inventoryFlags[NUM_INVENTORY_ITEMS];
uint8 _holomapFlags[MAX_HOLO_POS_2];
char _sceneName[30] {};
TextId _gameListChoice[10]; // inGameMenuData
int32 _gameNbChoices = 0; // numOfOptionsInChoice
TextId _gameChoice = TextId::kNone; // inGameMenuAnswer
void setDarts(int16 value) {
setGameFlag(InventoryItems::kiDart, value);
}
void addDart() {
int16 old = _listFlagGame[InventoryItems::kiDart];
++old;
setGameFlag(InventoryItems::kiDart, old);
}
inline bool inventoryDisabled() const {
return hasGameFlag(GAMEFLAG_INVENTORY_DISABLED) != 0;
}
inline bool hasOpenedFunfrocksSafe() const {
return hasGameFlag(30) != 0;
}
// Arrived on the hamalayi with the rebels
inline bool hasArrivedHamalayi() const {
return hasGameFlag(90) != 0;
}
inline bool hasItem(InventoryItems item) const {
return hasGameFlag(item) != 0;
}
inline void giveItem(InventoryItems item) {
setGameFlag(item, 1);
}
inline void removeItem(InventoryItems item) {
setGameFlag(item, 0);
}
void clearGameFlags();
int16 hasGameFlag(uint8 index) const;
void setGameFlag(uint8 index, int16 value);
int16 setKeys(int16 value);
int16 setGas(int16 value);
int16 setLeafs(int16 value);
int16 setKashes(int16 value);
int16 setZlitos(int16 value);
int16 setMagicPoints(int16 val);
int16 setMaxMagicPoints();
int16 setLeafBoxes(int16 val);
void handleLateGameItems();
void addGas(int16 value);
void addKeys(int16 val);
void addKashes(int16 val);
void addMagicPoints(int16 val);
void addLeafs(int16 val);
void addLeafBoxes(int16 val);
/** Initialize all engine variables */
void initEngineVars();
/** Initialize engine 3D projections */
void init3DGame();
void doFoundObj(InventoryItems item);
void giveUp();
bool loadGame(Common::SeekableReadStream *file);
bool saveGame(Common::WriteStream *file);
void gameAskChoice(TextId choiceIdx);
void processGameoverAnimation();
};
} // namespace TwinE
#endif

115
engines/twine/scene/graph.h Normal file
View 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 TWINE_SCENE_GRAPH_H
#define TWINE_SCENE_GRAPH_H
#include "graphics/screen.h"
#include "twine/shared.h"
namespace TwinE {
// WARNING: Rewrite this function to have better performance
inline bool drawGraph(int32 posX, int32 posY, const uint8 *pGraph, bool isSprite, Graphics::Screen &frontVideoBuffer, const Common::Rect &clip) { // AffGraph
if (!clip.isValidRect()) {
return false;
}
const int32 left = posX + pGraph[2];
if (left >= clip.right) {
return false;
}
const int32 top = posY + pGraph[3];
if (top >= clip.bottom) {
return false;
}
const int32 right = pGraph[0] + left;
if (right < clip.left) {
return false;
}
const int32 bottom = pGraph[1] + top;
if (bottom < clip.top) {
return false;
}
const int32 maxY = MIN(bottom, (int32)clip.bottom);
pGraph += 4; // skip the header
int32 x = left;
// if (left >= textWindowLeft-2 && top >= textWindowTop-2 && right <= textWindowRight-2 && bottom <= textWindowBottom-2) // crop
{
for (int32 y = top; y < maxY; ++y) {
const uint8 rleAmount = *pGraph++;
for (int32 run = 0; run < rleAmount; ++run) {
const uint8 rleMask = *pGraph++;
const uint8 iterations = bits(rleMask, 0, 6) + 1;
const uint8 opCode = bits(rleMask, 6, 2);
if (opCode == 0) {
x += iterations;
continue;
}
if (y < clip.top || x >= clip.right || x + iterations < clip.left) {
if (opCode == 1) {
pGraph += iterations;
} else {
++pGraph;
}
x += iterations;
continue;
}
if (opCode == 1) { // 0x40
uint8 *out = (uint8 *)frontVideoBuffer.getBasePtr(x, y);
for (uint8 i = 0; i < iterations; i++) {
if (x >= clip.left && x < clip.right) {
*out = *pGraph;
}
++out;
++x;
++pGraph;
}
} else {
const uint8 pixel = *pGraph++;
uint8 *out = (uint8 *)frontVideoBuffer.getBasePtr(x, y);
for (uint8 i = 0; i < iterations; i++) {
if (x >= clip.left && x < clip.right) {
*out = pixel;
}
++out;
++x;
}
}
}
x = left;
}
}
Common::Rect rect(left, top, right, bottom);
frontVideoBuffer.addDirtyRect(rect);
return true;
}
} // namespace TwinE
#endif // TWINE_SCENE_GRAPH_H

View File

@@ -0,0 +1,798 @@
/* 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/endian.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "twine/debugger/debug_state.h"
#include "twine/menu/interface.h"
#include "twine/parser/blocklibrary.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/collision.h"
#include "twine/scene/graph.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
#define CELLING_GRIDS_START_INDEX 120
namespace TwinE {
Grid::Grid(TwinEEngine *engine) : _engine(engine) {
_blockBufferSize = SIZE_CUBE_X * SIZE_CUBE_Z * SIZE_CUBE_Y * sizeof(BlockEntry);
_bufCube = (uint8 *)malloc(_blockBufferSize);
}
Grid::~Grid() {
free(_bufCube);
for (int32 i = 0; i < ARRAYSIZE(_brickMaskTable); i++) {
free(_brickMaskTable[i]);
}
for (int32 i = 0; i < ARRAYSIZE(_bufferBrick); i++) {
free(_bufferBrick[i]);
}
free(_currentGrid);
free(_nbBrickColon);
free(_listBrickColon);
}
void Grid::init(int32 w, int32 h) {
const int32 numbrickentries = (1 + (w + 24) / 24);
const size_t brickDataBufferSize = numbrickentries * MAX_BRICKS * sizeof(BrickEntry);
_listBrickColon = (BrickEntry *)malloc(brickDataBufferSize);
_brickInfoBufferSize = numbrickentries * sizeof(int16);
_nbBrickColon = (int16 *)malloc(_brickInfoBufferSize);
}
void Grid::copyMask(int32 index, int32 x, int32 y, const Graphics::ManagedSurface &buffer) {
if (_engine->_debugState->_disableGridRendering) {
return;
}
uint8 *ptr = _brickMaskTable[index];
int32 left = x + *(ptr + 2);
int32 top = y + *(ptr + 3);
int32 right = *ptr + left - 1;
int32 bottom = *(ptr + 1) + top - 1;
if (left > _engine->_interface->_clip.right || right < _engine->_interface->_clip.left || bottom < _engine->_interface->_clip.top || top > _engine->_interface->_clip.bottom) {
return;
}
ptr += 4;
int32 absX = left;
int32 absY = top;
int32 vSize = (bottom - top) + 1;
if (vSize <= 0) {
return;
}
int32 offset = -((right - left) - _engine->width()) - 1;
right++;
bottom++;
// if line on top aren't in the blitting area...
if (absY < _engine->_interface->_clip.top) {
int numOfLineToRemove = _engine->_interface->_clip.top - absY;
vSize -= numOfLineToRemove;
if (vSize <= 0) {
return;
}
absY += numOfLineToRemove;
do {
int lineDataSize;
lineDataSize = *(ptr++);
ptr += lineDataSize;
} while (--numOfLineToRemove);
}
// reduce the vSize to remove lines on bottom
if (absY + vSize - 1 > _engine->_interface->_clip.bottom) {
vSize = _engine->_interface->_clip.bottom - absY + 1;
if (vSize <= 0) {
return;
}
}
uint8 *outPtr = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(left, absY);
const uint8 *inPtr = (const uint8 *)buffer.getBasePtr(left, absY);
do {
int32 height = *(ptr++);
do {
int32 width = *(ptr++); // skip size
outPtr += width;
inPtr += width;
absX += width;
height--;
if (!height) {
break;
}
width = *(ptr++); // copy size
for (int32 j = 0; j < width; j++) {
if (absX >= _engine->_interface->_clip.left && absX <= _engine->_interface->_clip.right) {
*outPtr = *inPtr;
}
absX++;
outPtr++;
inPtr++;
}
} while (--height);
absX = left;
outPtr += offset;
inPtr += offset;
} while (--vSize);
}
const BrickEntry *Grid::getBrickEntry(int32 j, int32 i) const {
return &_listBrickColon[j * MAX_BRICKS + i];
}
void Grid::drawOverBrick(int32 x, int32 y, int32 z) {
const int32 startCol = ((_engine->_interface->_clip.left + 24) / 24) - 1;
const int32 endCol = ((_engine->_interface->_clip.right + 24) / 24);
for (int32 col = startCol; col <= endCol; col++) {
for (int32 i = 0; i < _nbBrickColon[col]; i++) {
const BrickEntry *currBrickEntry = getBrickEntry(col, i);
if (currBrickEntry->posY + 38 > _engine->_interface->_clip.top && currBrickEntry->posY <= _engine->_interface->_clip.bottom && currBrickEntry->y >= y) {
if (currBrickEntry->x + currBrickEntry->z > z + x) {
copyMask(currBrickEntry->index, (col * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
}
}
}
}
}
void Grid::drawOverBrick3(int32 x, int32 y, int32 z) {
const int32 startCol = ((_engine->_interface->_clip.left + 24) / 24) - 1;
const int32 endCol = (_engine->_interface->_clip.right + 24) / 24;
for (int32 col = startCol; col <= endCol; col++) {
for (int32 i = 0; i < _nbBrickColon[col]; i++) {
const BrickEntry *currBrickEntry = getBrickEntry(col, i);
if (currBrickEntry->posY + 38 > _engine->_interface->_clip.top && currBrickEntry->posY <= _engine->_interface->_clip.bottom && currBrickEntry->y >= y) {
if (currBrickEntry->x == x && currBrickEntry->z == z) {
copyMask(currBrickEntry->index, (col * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
}
if (currBrickEntry->x > x || currBrickEntry->z > z) {
copyMask(currBrickEntry->index, (col * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
}
}
}
}
}
void Grid::processGridMask(const uint8 *buffer, uint8 *ptr) {
const uint8 width = *buffer++;
uint8 height = *buffer++;
const uint8 offsetX = *buffer++;
const uint8 offsetY = *buffer++;
const int32 maxY = offsetY + height;
*ptr++ = width;
*ptr++ = height;
*ptr++ = offsetX;
*ptr++ = offsetY;
uint8 *targetPtrPos = ptr;
for (int32 y = offsetY; y < maxY; ++y) {
uint8 numOfBlock = 0;
uint8 opaquePixels = 0;
uint8 *numOfBlockTargetPtr = targetPtrPos;
targetPtrPos++;
const uint8 numRuns = *buffer++;
// the first time isn't skip. the skip size is 0 in that case
if (bits(*buffer, 6, 2) != 0) {
*targetPtrPos++ = 0;
numOfBlock++;
}
for (uint8 run = 0; run < numRuns; ++run) {
const uint8 runSpec = *buffer++;
const uint8 runLength = bits(runSpec, 0, 6) + 1;
const uint8 type = bits(runSpec, 6, 2);
if (type == 2) {
opaquePixels += runLength;
buffer++;
} else if (type == 1) { // 0x40
opaquePixels += runLength;
buffer += runLength;
} else { // skip (type 3)
if (opaquePixels) {
*targetPtrPos++ = opaquePixels; // write down the number of pixel passed so far
numOfBlock++;
opaquePixels = 0;
}
*targetPtrPos++ = runLength; // write skip
numOfBlock++;
}
}
if (opaquePixels) {
*targetPtrPos++ = opaquePixels;
numOfBlock++;
opaquePixels = 0;
}
*numOfBlockTargetPtr = numOfBlock;
}
}
void Grid::createGridMask() {
for (int32 b = 0; b < NUM_BRICKS; b++) {
if (!_brickUsageTable[b]) {
continue;
}
if (_brickMaskTable[b]) {
free(_brickMaskTable[b]);
}
_brickMaskTable[b] = (uint8 *)malloc(_brickSizeTable[b]);
processGridMask(_bufferBrick[b], _brickMaskTable[b]);
}
}
void Grid::getSpriteSize(int32 offset, int32 *width, int32 *height, const uint8 *spritePtr) {
spritePtr += READ_LE_INT32(spritePtr + offset * 4);
*width = *spritePtr;
*height = *(spritePtr + 1);
}
void Grid::loadGridBricks() {
uint32 firstBrick = 60000;
uint32 lastBrick = 0;
uint32 currentBllEntryIdx = 1;
memset(_brickSizeTable, 0, sizeof(_brickSizeTable));
memset(_brickUsageTable, 0, sizeof(_brickUsageTable));
// get block libraries usage bits
const uint8 *ptrToBllBits = _currentGrid + (_currentGridSize - 32);
// for all bits under the 32bytes (256bits)
for (uint32 i = 1; i < 256; i++) {
const uint8 currentBitByte = *(ptrToBllBits + (i / 8));
const uint8 currentBitMask = 1 << (7 - (i & 7));
if (currentBitByte & currentBitMask) {
const BlockData *currentBllPtr = getBlockLibrary(currentBllEntryIdx);
for (const BlockDataEntry &entry : currentBllPtr->entries) {
uint16 brickIdx = entry.brickIdx;
if (!brickIdx) {
continue;
}
brickIdx--;
if (brickIdx <= firstBrick) {
firstBrick = brickIdx;
}
if (brickIdx > lastBrick) {
lastBrick = brickIdx;
}
_brickUsageTable[brickIdx] = 1;
}
}
++currentBllEntryIdx;
}
for (uint32 i = firstBrick; i <= lastBrick; i++) {
if (!_brickUsageTable[i]) {
free(_bufferBrick[i]);
_bufferBrick[i] = nullptr;
continue;
}
_brickSizeTable[i] = HQR::getAllocEntry(&_bufferBrick[i], Resources::HQR_LBA_BRK_FILE, i);
if (_brickSizeTable[i] == 0) {
warning("Failed to load isometric brick index %i", i);
}
}
}
void Grid::decompColumn(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize) { // DecompColonne
Common::MemoryReadStream stream(gridEntry, gridEntrySize);
Common::MemoryWriteStream outstream(dest, destSize);
int32 brickCount = stream.readByte();
do {
const int32 flag = stream.readByte();
const int32 blockCount = bits(flag, 0, 6) + 1;
const int32 type = bits(flag, 6, 2);
if (type == 0) {
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(0);
}
} else if (type == 1) { // 0x40
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(stream.readUint16LE());
}
} else {
const uint16 gridIdx = stream.readUint16LE();
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(gridIdx);
}
}
assert(!outstream.err());
assert(!stream.err());
} while (--brickCount);
}
void Grid::calcGraphMsk(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize) {
Common::MemoryReadStream stream(gridEntry, gridEntrySize);
Common::SeekableMemoryWriteStream outstream(dest, destSize);
int32 brickCount = stream.readByte();
do {
const int32 flag = stream.readByte();
const int32 blockCount = bits(flag, 0, 6) + 1;
const int32 type = bits(flag, 6, 2);
if (type == 0) {
for (int32 i = 0; i < blockCount; i++) {
outstream.seek(outstream.pos() + 2);
}
} else if (type == 1) {
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(stream.readUint16LE());
}
} else {
const uint16 gridIdx = stream.readUint16LE();
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(gridIdx);
}
}
assert(!outstream.err());
assert(!stream.err());
} while (--brickCount);
}
void Grid::copyMapToCube() {
int32 blockOffset = 0;
for (int32 z = 0; z < SIZE_CUBE_Z; z++) {
const int32 gridIdx = z * SIZE_CUBE_X;
for (int32 x = 0; x < SIZE_CUBE_X; x++) {
const int32 gridOffset = READ_LE_UINT16(_currentGrid + 2 * (x + gridIdx));
decompColumn(_currentGrid + gridOffset, _currentGridSize - gridOffset, _bufCube + blockOffset, _blockBufferSize - blockOffset);
blockOffset += 2 * SIZE_CUBE_Y;
}
}
}
void Grid::createCellingGridMap(const uint8 *gridPtr, int32 gridPtrSize) { // MixteMapToCube
int32 currGridOffset = 0;
int32 blockOffset = 0;
for (int32 z = 0; z < SIZE_CUBE_Z; z++) {
const uint8 *tempGridPtr = gridPtr + currGridOffset;
for (int32 x = 0; x < SIZE_CUBE_X; x++) {
const int gridOffset = READ_LE_UINT16(tempGridPtr);
tempGridPtr += 2;
calcGraphMsk(gridPtr + gridOffset, gridPtrSize - gridOffset, _bufCube + blockOffset, _blockBufferSize - blockOffset);
blockOffset += 2 * SIZE_CUBE_Y;
}
currGridOffset += SIZE_CUBE_X + SIZE_CUBE_Z;
}
}
bool Grid::initGrid(int32 index) {
// load grids from file
_currentGridSize = HQR::getAllocEntry(&_currentGrid, Resources::HQR_LBA_GRI_FILE, index);
if (_currentGridSize == 0) {
warning("Failed to load grid index: %i", index);
return false;
}
// load layouts from file
if (!_currentBlockLibrary.loadFromHQR(Resources::HQR_LBA_BLL_FILE, index, _engine->isLBA1())) {
warning("Failed to load block library index: %i", index);
return false;
}
loadGridBricks();
createGridMask();
copyMapToCube();
return true;
}
bool Grid::initCellingGrid(int32 index) { // IncrustGrm
uint8 *gridPtr = nullptr;
// load grids from file
const int realIndex = index + CELLING_GRIDS_START_INDEX;
const int32 gridSize = HQR::getAllocEntry(&gridPtr, Resources::HQR_LBA_GRI_FILE, realIndex);
if (gridSize == 0) {
warning("Failed to load grid index %i", realIndex);
return false;
}
createCellingGridMap(gridPtr, gridSize);
free(gridPtr);
_engine->_redraw->_firstTime = true;
return true;
}
bool Grid::drawGraph(int32 index, int32 posX, int32 posY) { // AffGraph
return drawGraph(posX, posY, _bufferBrick[index], false);
}
bool Grid::drawGraph(int32 index, int32 posX, int32 posY, const uint8 *ptr) { // AffGraph
ptr = ptr + READ_LE_INT32(ptr + index * 4); // GetGraph, GetMask
return drawGraph(posX, posY, ptr, true);
}
bool Grid::drawSprite(int32 posX, int32 posY, const SpriteData &ptr, int spriteIndex) {
const int32 left = posX + ptr.offsetX(spriteIndex);
if (left >= _engine->_interface->_clip.right) {
return false;
}
const int32 right = ptr.surface(spriteIndex).w + left;
if (right < _engine->_interface->_clip.left) {
return false;
}
const int32 top = posY + ptr.offsetY(spriteIndex);
if (top >= _engine->_interface->_clip.bottom) {
return false;
}
const int32 bottom = ptr.surface(spriteIndex).h + top;
if (bottom < _engine->_interface->_clip.top) {
return false;
}
const Common::Point pos(left, top);
_engine->_frontVideoBuffer.transBlitFrom(ptr.surface(spriteIndex), pos);
return true;
}
bool Grid::drawGraph(int32 posX, int32 posY, const uint8 *pGraph, bool isSprite) {
if (_engine->_debugState->_disableGridRendering) {
return false;
}
const Common::Rect &clip = _engine->_interface->_clip;
TwineScreen &frontVideoBuffer = _engine->_frontVideoBuffer;
return TwinE::drawGraph(posX, posY, pGraph, isSprite, frontVideoBuffer, clip);
}
const uint8 *Grid::getBlockBufferGround(const IVec3 &pos, int32 &ground) {
const IVec3 &collision = updateCollisionCoordinates(pos.x, pos.y, pos.z);
const uint8 *ptr = _bufCube + collision.y * 2 + collision.x * SIZE_CUBE_Y * 2 + collision.z * SIZE_CUBE_X * SIZE_CUBE_Y * 2;
int32 collisionY = collision.y;
while (collisionY) {
if (READ_LE_INT16(ptr)) { // found the ground - sizeof(BlockEntry);
break;
}
collisionY--;
ptr -= sizeof(int16);
}
_engine->_collision->_collision.y = collisionY;
ground = (int16)((collisionY + 1) * SIZE_BRICK_Y);
return ptr;
}
const BlockDataEntry *Grid::getAdrBlock(int32 blockIdx, int32 brickIdx) const {
const BlockData *blockPtr = getBlockLibrary(blockIdx);
return &blockPtr->entries[brickIdx];
}
const BlockData *Grid::getBlockLibrary(int32 blockIdx) const {
return _currentBlockLibrary.getLayout(blockIdx - 1);
}
void Grid::map2Screen(int32 x, int32 y, int32 z, int32 &posx, int32 &posy) const {
posx = (x - z) * 24 + _engine->width() / 2 - SIZE_CUBE_X / 2;
posy = ((x + z) * 12) - (y * 15) + _engine->height() / 2 - SIZE_CUBE_Y;
}
void Grid::drawBrickBlock(int32 blockIdx, int32 brickBlockIdx, int32 x, int32 y, int32 z) { // AffBrickBlock
const BlockDataEntry *blockPtr = getAdrBlock(blockIdx, brickBlockIdx);
const uint8 brickShape = blockPtr->brickShape;
const uint8 brickSound = blockPtr->brickType;
const uint16 numBrick = blockPtr->brickIdx;
if (!numBrick) {
return;
}
int32 brickPixelPosX = 0;
int32 brickPixelPosY = 0;
map2Screen(x - _startCube.x, y - _startCube.y, z - _startCube.z, brickPixelPosX, brickPixelPosY);
if (brickPixelPosX < -24) {
return;
}
if (brickPixelPosX >= _engine->width()) {
return;
}
if (brickPixelPosY < -38) {
return;
}
if (brickPixelPosY >= _engine->height()) {
return;
}
// draw the background brick
drawGraph(numBrick - 1, brickPixelPosX, brickPixelPosY);
int32 col = (brickPixelPosX + 24) / 24;
if (_nbBrickColon[col] >= MAX_BRICKS) {
warning("GRID: brick buffer exceeded");
return;
}
BrickEntry *pColonBrick = &_listBrickColon[col * MAX_BRICKS + _nbBrickColon[col]];
pColonBrick->x = x;
pColonBrick->y = y;
pColonBrick->z = z;
pColonBrick->posX = brickPixelPosX;
pColonBrick->posY = brickPixelPosY;
pColonBrick->index = numBrick - 1;
pColonBrick->shape = brickShape;
pColonBrick->sound = brickSound;
_nbBrickColon[col]++;
}
void Grid::redrawGrid() { // AffGrille
_worldCube.x = _startCube.x * SIZE_BRICK_XZ;
_worldCube.y = _startCube.y * SIZE_BRICK_Y;
_worldCube.z = _startCube.z * SIZE_BRICK_XZ;
const IVec3 &projPos = _engine->_renderer->projectPoint(-_worldCube);
_engine->_redraw->_projPosScreen.x = projPos.x;
_engine->_redraw->_projPosScreen.y = projPos.y;
memset(_nbBrickColon, 0, _brickInfoBufferSize);
if (!_engine->_scene->_flagRenderGrid) {
return;
}
_engine->_screens->clearScreen();
for (int32 z = 0; z < SIZE_CUBE_Z; z++) {
for (int32 x = 0; x < SIZE_CUBE_X; x++) {
for (int32 y = 0; y < SIZE_CUBE_Y; y++) {
const BlockEntry entry = getBlockEntry(x, y, z);
if (entry.blockIdx) {
drawBrickBlock(entry.blockIdx, entry.brickBlockIdx, x, y, z);
}
}
}
}
}
BlockEntry Grid::getBlockEntry(int32 xmap, int32 ymap, int32 zmap) const {
const uint8 *pCube = _bufCube;
const int32 size = 2; // sizeof(BlockEntry);
pCube += xmap * SIZE_CUBE_Y * size;
pCube += ymap * size;
pCube += zmap * (SIZE_CUBE_X * SIZE_CUBE_Y * size);
BlockEntry entry;
entry.blockIdx = *pCube;
entry.brickBlockIdx = *(pCube + 1);
return entry;
}
ShapeType Grid::worldColBrick(int32 x, int32 y, int32 z) {
const IVec3 &collision = updateCollisionCoordinates(x, y, z);
if (collision.y <= -1) {
return ShapeType::kSolid;
}
if (collision.x < 0 || collision.x >= SIZE_CUBE_X) {
return ShapeType::kNone;
}
if (collision.y < 0 || collision.y >= SIZE_CUBE_Y) {
return ShapeType::kNone;
}
if (collision.z < 0 || collision.z >= SIZE_CUBE_Z) {
return ShapeType::kNone;
}
const BlockEntry entry = getBlockEntry(collision.x, collision.y, collision.z);
if (entry.blockIdx) {
const BlockDataEntry *blockPtr = getAdrBlock(entry.blockIdx, entry.brickBlockIdx);
return (ShapeType)blockPtr->brickShape;
}
return (ShapeType)entry.brickBlockIdx; // eventually transparent color
}
const IVec3 &Grid::updateCollisionCoordinates(int32 x, int32 y, int32 z) {
_engine->_collision->_collision.x = (x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_collision->_collision.y = y / SIZE_BRICK_Y;
_engine->_collision->_collision.z = (z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
return _engine->_collision->_collision;
}
bool Grid::shouldCheckWaterCol(int32 actorIdx) const {
if (actorIdx == OWN_ACTOR_SCENE_INDEX) {
ActorStruct *ptrobj = _engine->_scene->getActor(actorIdx);
if (_engine->_actor->_heroBehaviour != HeroBehaviourType::kProtoPack
&& ptrobj->_flags.bComputeCollisionWithFloor
&& !ptrobj->_flags.bIsInvisible
&& !ptrobj->_workFlags.bIsFalling
&& ptrobj->_carryBy == -1) {
return true;
}
}
return false;
}
ShapeType Grid::worldColBrickFull(int32 x, int32 y, int32 z, int32 y2, int32 actorIdx) {
const IVec3 &collision = updateCollisionCoordinates(x, y, z);
if (collision.y <= -1) {
return ShapeType::kSolid;
}
if (collision.x < 0 || collision.x >= SIZE_CUBE_X || collision.z < 0 || collision.z >= SIZE_CUBE_Z) {
return ShapeType::kNone;
}
bool checkWater = shouldCheckWaterCol(actorIdx);
uint8 *pCube = _bufCube;
pCube += collision.x * SIZE_CUBE_Y * 2;
pCube += collision.y * 2;
pCube += collision.z * (SIZE_CUBE_X * SIZE_CUBE_Y * 2);
uint8 block = *pCube;
ShapeType brickShape;
const uint8 tmpBrickIdx = *(pCube + 1);
if (block) {
const BlockDataEntry *blockPtr = getAdrBlock(block, tmpBrickIdx);
if (checkWater && blockPtr->brickType == WATER_BRICK) {
brickShape = ShapeType::kSolid; // full collision
} else {
brickShape = (ShapeType)blockPtr->brickShape;
}
} else {
brickShape = (ShapeType)tmpBrickIdx; // maybe transparency
if (checkWater) {
uint8 *pCode = pCube;
for (y = collision.y - 1; y >= 0; y--) {
pCode -= 2;
uint8 code = *pCode;
if (code) {
const BlockDataEntry *blockPtr = getAdrBlock(block, 0);
if (blockPtr->brickType == WATER_BRICK) {
// Special check mount funfrock
if (_engine->_scene->_numCube != LBA1SceneId::Polar_Island_on_the_rocky_peak) {
// full collision
return ShapeType::kSolid;
}
}
break; // stop parsing at first encountered brick
}
}
}
}
int32 ymax = (y2 + (SIZE_BRICK_Y - 1)) / SIZE_BRICK_Y;
// check full height
for (y = collision.y; ymax > 0 && y < (SIZE_CUBE_Y - 1); --ymax, y++) {
pCube += 2;
if (READ_LE_INT16(pCube)) {
return ShapeType::kSolid;
}
}
return brickShape;
}
uint8 Grid::worldCodeBrick(int32 x, int32 y, int32 z) {
uint8 code = 0xF0U;
if (y > -1) {
const IVec3 &collision = updateCollisionCoordinates(x, y, z);
const BlockEntry entry = getBlockEntry(collision.x, collision.y, collision.z);
if (entry.blockIdx) {
const BlockDataEntry *blockPtr = getAdrBlock(entry.blockIdx, entry.brickBlockIdx);
code = blockPtr->brickType;
}
}
return code;
}
void Grid::centerOnActor(const ActorStruct *actor) {
_startCube.x = (actor->_posObj.x + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
_startCube.y = (actor->_posObj.y + SIZE_BRICK_Y) / SIZE_BRICK_Y;
_startCube.z = (actor->_posObj.z + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
_engine->_redraw->_firstTime = true;
}
void Grid::centerScreenOnActor() {
if (_engine->_cameraZone) {
return;
}
if (_engine->_debugState->_useFreeCamera) {
return;
}
ActorStruct *actor = _engine->_scene->getActor(_engine->_scene->_numObjFollow);
const IVec3 projPos = _engine->_renderer->projectPoint(actor->_posObj.x - (_startCube.x * SIZE_BRICK_XZ),
actor->_posObj.y - (_startCube.y * SIZE_BRICK_Y),
actor->_posObj.z - (_startCube.z * SIZE_BRICK_XZ));
// TODO: these border values should get scaled for higher resolutions
if (projPos.x < 80 || projPos.x >= _engine->width() - 60 || projPos.y < 80 || projPos.y >= _engine->height() - 50) {
_startCube.x = ((actor->_posObj.x + SIZE_BRICK_Y) / SIZE_BRICK_XZ) + (((actor->_posObj.x + SIZE_BRICK_Y) / SIZE_BRICK_XZ) - _startCube.x) / 2;
_startCube.y = actor->_posObj.y / SIZE_BRICK_Y;
_startCube.z = ((actor->_posObj.z + SIZE_BRICK_Y) / SIZE_BRICK_XZ) + (((actor->_posObj.z + SIZE_BRICK_Y) / SIZE_BRICK_XZ) - _startCube.z) / 2;
if (_startCube.x >= SIZE_CUBE_X) {
_startCube.x = SIZE_CUBE_X - 1;
}
if (_startCube.z >= SIZE_CUBE_Z) {
_startCube.z = SIZE_CUBE_Z - 1;
}
_engine->_redraw->_firstTime = true;
}
}
} // namespace TwinE

308
engines/twine/scene/grid.h Normal file
View File

@@ -0,0 +1,308 @@
/* 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 TWINE_SCENE_GRID_H
#define TWINE_SCENE_GRID_H
#define WATER_BRICK (0xF1) // CJ_WATER
#include "common/scummsys.h"
#include "twine/parser/blocklibrary.h"
#include "twine/parser/sprite.h"
#include "twine/shared.h"
namespace Graphics {
class ManagedSurface;
}
namespace TwinE {
class ActorStruct;
/** Block fragment entry */
struct BlockEntry {
/** Block library index */
uint8 blockIdx = 0;
/** Brick index inside the block library */
uint8 brickBlockIdx = 0;
};
/** Brick entry data */
struct BrickEntry { // T_COLONB
/** Brick X position in screen */
int16 x = 0; //z
/** Brick Y position in screen */
int16 y = 0;
/** Brick Z position in screen */
int16 z = 0; // x
/** Brick pixel X position */
int16 posX = 0;
/** Brick pixel Y position */
int16 posY = 0;
/** Brick index */
int16 index = 0;
/** Brick shape type */
uint8 shape = 0;
/** Brick sound type */
uint8 sound = 0;
};
/** Total number of bricks allowed in the game */
#define NUM_BRICKS 9000
/** Grid X size */
#define SIZE_CUBE_X 64
/** Grid Y size */
#define SIZE_CUBE_Y 25
/** Grid Z size */
#define SIZE_CUBE_Z SIZE_CUBE_X
#define ISO_SCALE 512
#define SIZE_BRICK_XZ 512
#define SIZE_BRICK_Y 256
#define DEMI_BRICK_XZ 256
#define DEMI_BRICK_Y 128
// short max 32767 0x7FFF
// 32256 0x7E00
// 32000 0x7D00
#define SCENE_SIZE_MAX (SIZE_BRICK_XZ * (SIZE_CUBE_X - 1))
// short min -32768
#define SCENE_SIZE_MIN (-SIZE_BRICK_XZ * SIZE_CUBE_X)
#define SCENE_SIZE_HALF (SIZE_BRICK_XZ * SIZE_CUBE_X / 2)
#define SCENE_SIZE_HALFF (SIZE_BRICK_XZ * SIZE_CUBE_X / 2.0f)
#define MAX_BRICKS 150
class TwinEEngine;
class Grid {
private:
TwinEEngine *_engine;
/**
* Draw a specific brick in the grid column according with the block index
* @param blockIdx block library index
* @param brickBlockIdx brick index inside the block
* @param x column x position
* @param y column y position
* @param z column z position
*/
void drawBrickBlock(int32 blockIdx, int32 brickBlockIdx, int32 x, int32 y, int32 z);
/**
* Get brick position in the screen
* @param x column x position in the current camera
* @param y column y position in the current camera
* @param z column z position in the current camera
*/
void map2Screen(int32 x, int32 y, int32 z, int32 &_brickPixelPosX, int32 &_brickPixelPosY) const;
/**
* Create celling grid map from celling grid to block library buffer
* @param gridPtr celling grid buffer pointer
*/
void createCellingGridMap(const uint8 *gridPtr, int32 gridPtrSize);
/**
* Create grid Y column in block buffer
* @param gridEntry current grid index
* @param dest destination block buffer
*/
void calcGraphMsk(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize);
/**
* Create grid Y column in block buffer
* @param gridEntry current grid index
* @param dest destination block buffer
*/
void decompColumn(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize);
/**
* Load grid bricks according with block librarie usage
*/
void loadGridBricks();
/** Create grid masks to allow display actors over the bricks */
void createGridMask();
/**
* Process brick masks to allow actors to display over the bricks
* @param buffer brick pointer buffer
* @param ptr brick mask pointer buffer
*/
void processGridMask(const uint8 *buffer, uint8 *ptr);
/**
* Copy grid mask to allow actors to display over the bricks
* @param index current brick index
* @param x grid X coordinate
* @param y grid Y coordinate
* @param buffer work video buffer
*/
void copyMask(int32 index, int32 x, int32 y, const Graphics::ManagedSurface &buffer);
/** Table with all loaded bricks */
uint8 *_bufferBrick[NUM_BRICKS]{nullptr};
/** Table with all loaded bricks masks */
uint8 *_brickMaskTable[NUM_BRICKS]{nullptr};
/** Table with all loaded bricks sizes */
uint32 _brickSizeTable[NUM_BRICKS]{0};
/** Table with all loaded bricks usage */
uint8 _brickUsageTable[NUM_BRICKS]{0};
/** Current grid pointer */
int32 _currentGridSize = 0;
uint8 *_currentGrid = nullptr;
/** Current block library */
BlockLibraryData _currentBlockLibrary;
/** Brick data buffer */
BrickEntry *_listBrickColon = nullptr;
/** Brick info buffer */
int16 *_nbBrickColon = nullptr;
int32 _brickInfoBufferSize = 0;
/** Celling grid brick block buffer */
int32 _blockBufferSize = 0;
uint8 *_bufCube = nullptr;
const BrickEntry* getBrickEntry(int32 j, int32 i) const;
const IVec3 &updateCollisionCoordinates(int32 x, int32 y, int32 z);
BlockEntry getBlockEntry(int32 xmap, int32 ymap, int32 zmap) const;
bool shouldCheckWaterCol(int32 actorIdx) const;
public:
Grid(TwinEEngine *engine);
~Grid();
void init(int32 w, int32 h);
/**
* search down until either ground is found or lower border of the cube is reached
*/
const uint8 *getBlockBufferGround(const IVec3 &pos, int32 &ground);
/** New grid camera x, y and z coordinates */
IVec3 _startCube; // StartXCube, StartYCube, StartZCube
/** Current grid camera x, y and z coordinates */
IVec3 _worldCube; // WorldXCube WorldYCube
int32 _addBetaCam = 0;
/** Flag to know if the engine is using celling grids */
int16 _zoneGrm = 0;
/** Current celling grid index */
int16 _indexGrm = 0;
/**
* Draw 3D actor over bricks
* @param x actor.x coordinate
* @param y actor.y coordinate
* @param z actor.z coordinate
*/
void drawOverBrick(int32 x, int32 y, int32 z);
/**
* Draw sprite actor over bricks
* @param x actor.x coordinate
* @param y actor.y coordinate
* @param z actor.z coordinate
*/
void drawOverBrick3(int32 x, int32 y, int32 z);
/**
* Get sprite width and height sizes
* @param offset sprite pointer offset
* @param width sprite width size
* @param height sprite height size
* @param spritePtr sprite buffer pointer
*/
void getSpriteSize(int32 offset, int32 *width, int32 *height, const uint8 *spritePtr);
/** recenter screen on followed actor automatically */
void centerScreenOnActor();
void centerOnActor(const ActorStruct* actor);
/**
* Draw brick sprite in the screen
* @param index brick index to draw
* @param posX brick X position to draw
* @param posY brick Y position to draw
*/
bool drawGraph(int32 index, int32 posX, int32 posY);
/**
* Draw sprite in the screen
* @param index sprite index to draw
* @param posX sprite X position to draw
* @param posY sprite Y position to draw
* @param spritePtr sprite buffer pointer to draw
*/
bool drawGraph(int32 index, int32 posX, int32 posY, const uint8 *spritePtr);
bool drawSprite(int32 posX, int32 posY, const SpriteData &ptr, int spriteIndex = 0);
/**
* Draw sprite or bricks in the screen according with the type
* @param posX sprite X position to draw
* @param posY sprite Y position to draw
* @param pGraph sprite buffer pointer to draw
* @param isSprite allows to identify if the sprite to display is brick or a single sprite
*/
bool drawGraph(int32 posX, int32 posY, const uint8 *pGraph, bool isSprite);
/**
* Get block library
* @param blockIdx block library index
* @return pointer to the current block index
*/
const BlockData *getBlockLibrary(int32 blockIdx) const;
const BlockDataEntry* getAdrBlock(int32 blockIdx, int32 tmpBrickIdx) const;
/** Create grid map from current grid to block library buffer */
void copyMapToCube();
/**
* Initialize grid (background scenearios)
* @param index grid index number
*/
bool initGrid(int32 index);
/**
* Initialize celling grid (background scenearios)
* @param index grid index number
*/
bool initCellingGrid(int32 index);
/** Redraw grid background */
void redrawGrid();
ShapeType worldColBrick(int32 x, int32 y, int32 z);
ShapeType worldColBrickFull(int32 x, int32 y, int32 z, int32 y2, int32 actorIdx);
uint8 worldCodeBrick(int32 x, int32 y, int32 z);
inline ShapeType worldColBrick(const IVec3 &pos) {
return worldColBrick(pos.x, pos.y, pos.z);
}
inline ShapeType worldColBrickFull(const IVec3 &pos, int32 y2, int32 actorIdx) {
return worldColBrickFull(pos.x, pos.y, pos.z, y2, actorIdx);
}
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,521 @@
/* 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 "twine/scene/movements.h"
#include "common/textconsole.h"
#include "twine/input.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/shadeangletab.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Movements::Movements(TwinEEngine *engine) : _engine(engine) {}
IVec3 Movements::getShadow(const IVec3 &pos) { // GetShadow
IVec3 shadowCoord;
const uint8 *ptr = _engine->_grid->getBlockBufferGround(pos, shadowCoord.y);
shadowCoord.x = pos.x;
shadowCoord.z = pos.z;
ShapeType shadowCollisionType;
const int32 blockIdx = *ptr;
if (blockIdx) {
const int32 brickIdx = *(ptr + 1);
const BlockDataEntry *blockPtr = _engine->_grid->getAdrBlock(blockIdx, brickIdx);
shadowCollisionType = (ShapeType)blockPtr->brickShape;
} else {
shadowCollisionType = ShapeType::kNone;
}
_engine->_collision->reajustPos(shadowCoord, shadowCollisionType);
return shadowCoord;
}
void Movements::initRealAngle(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr) {
movePtr->startValue = ClampAngle(startAngle);
movePtr->endValue = ClampAngle(endAngle);
movePtr->timeValue = ClampAngle(stepAngle);
movePtr->memoTicks = _engine->timerRef;
}
void Movements::clearRealAngle(ActorStruct *actorPtr) {
initRealAngle(actorPtr->_beta, actorPtr->_beta, LBAAngles::ANGLE_0, &actorPtr->realAngle);
}
void Movements::initRealValue(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr) {
movePtr->startValue = startAngle;
movePtr->endValue = endAngle;
movePtr->timeValue = stepAngle;
movePtr->memoTicks = _engine->timerRef;
}
int32 Movements::getAngle(int32 x0, int32 z0, int32 x1, int32 z1) {
#if 1
int32 difZ = z1 - z0;
const int32 newZ = difZ * difZ;
int32 difX = x1 - x0;
const int32 newX = difX * difX;
bool flag;
// Exchange X and Z
if (newX < newZ) {
const int32 tmpEx = difX;
difX = difZ;
difZ = tmpEx;
flag = true;
} else {
flag = false;
}
_targetActorDistance = (int32)sqrt((float)(newX + newZ));
if (!_targetActorDistance) {
return 0;
}
const int32 destAngle = (difZ * SCENE_SIZE_HALF) / _targetActorDistance;
int32 startAngle = LBAAngles::ANGLE_0;
// stopAngle = LBAAngles::ANGLE_90;
const int16 *shadeAngleTab3(&sinTab[LBAAngles::ANGLE_135]);
while (shadeAngleTab3[startAngle] > destAngle) {
startAngle++;
}
if (shadeAngleTab3[startAngle] != destAngle) {
if ((shadeAngleTab3[startAngle - 1] + shadeAngleTab3[startAngle]) / 2 <= destAngle) {
startAngle--;
}
}
int32 finalAngle = LBAAngles::ANGLE_45 + startAngle;
if (difX <= 0) {
finalAngle = -finalAngle;
}
if (flag) {
finalAngle = -finalAngle + LBAAngles::ANGLE_90;
}
return ClampAngle(finalAngle);
#else
z1 -= z0;
x1 -= x0;
const int32 x2 = x1 * x1;
const int32 z2 = z1 * z1;
_targetActorDistance = (int32)sqrt((float)(x2 + z2));
if (!_targetActorDistance) {
return 0;
}
if (z2 > x2) {
const int32 tmpEx = z1;
x1 = z1 | 1; // flag = 1
z1 = tmpEx;
} else {
x1 &= -2; // flag = 0
}
const int32 tmp = (z1 * SCENE_SIZE_HALF) / _targetActorDistance;
int32 start = LBAAngles::ANGLE_135;
int32 end = LBAAngles::ANGLE_135 + LBAAngles::ANGLE_90;
int32 diff = 0;
while (start < (end - 1)) {
int32 angle = (start + end) >> 1;
diff = tmp - sinTab[angle];
if (diff > 0) {
end = angle;
} else {
start = angle;
if (diff == 0) {
break;
}
}
}
if (diff) {
if (tmp <= ((sinTab[start] + sinTab[end]) >> 1)) {
start = end;
}
}
int32 angle = start - LBAAngles::ANGLE_90;
if (x1 < 0) {
angle = -angle;
}
if (x1 & 1) {
angle = LBAAngles::ANGLE_90 - angle;
}
return ClampAngle(angle);
#endif
}
void Movements::initRealAngleConst(int32 start, int32 end, int32 duration, RealValue *movePtr) const { // ManualRealAngle
const int16 cstart = ClampAngle(start);
const int16 cend = ClampAngle(end);
movePtr->startValue = cstart;
movePtr->endValue = cend;
const int16 numOfStep = (cstart - cend) * 64;
int32 t = ABS(numOfStep);
t /= 64;
t *= duration;
t /= 256;
movePtr->timeValue = (int16)t;
movePtr->memoTicks = _engine->timerRef;
}
void Movements::ChangedCursorKeys::update(TwinEEngine *engine) {
if (engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
leftChange = leftDown == 0;
leftDown = 1;
} else {
leftChange = leftDown;
leftDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::TurnRight)) {
rightChange = rightDown == 0;
rightDown = 1;
} else {
rightChange = rightDown;
rightDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
backwardChange = backwardDown == 0;
backwardDown = 1;
} else {
backwardChange = backwardDown;
backwardDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::MoveForward)) {
forwardChange = forwardDown == 0;
forwardDown = 1;
} else {
forwardChange = forwardDown;
forwardDown = 0;
}
}
void Movements::update() {
_previousChangedCursorKeys = _changedCursorKeys;
_previousLoopActionKey = _heroActionKey;
_heroActionKey = _engine->_input->isHeroActionActive();
_changedCursorKeys.update(_engine);
}
void Movements::processBehaviourExecution(int actorIdx) {
switch (_engine->_actor->_heroBehaviour) {
case HeroBehaviourType::kNormal:
_actionNormal = true;
break;
case HeroBehaviourType::kAthletic:
_engine->_animations->initAnim(AnimationTypes::kJump, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case HeroBehaviourType::kAggressive:
if (_engine->_actor->_combatAuto) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
_lastJoyFlag = true;
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
// TODO: previousLoopActionKey must be handled properly
if (!_previousLoopActionKey || actor->_genAnim == AnimationTypes::kStanding) {
const int32 aggresiveMode = _engine->getRandomNumber(3);
switch (aggresiveMode) {
case 0:
_engine->_animations->initAnim(AnimationTypes::kKick, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case 1:
_engine->_animations->initAnim(AnimationTypes::kRightPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case 2:
_engine->_animations->initAnim(AnimationTypes::kLeftPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
}
}
} else {
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
_engine->_animations->initAnim(AnimationTypes::kLeftPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
_engine->_animations->initAnim(AnimationTypes::kRightPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
} else if (_engine->_input->isActionActive(TwinEActionType::MoveForward)) {
_engine->_animations->initAnim(AnimationTypes::kKick, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
}
}
break;
case HeroBehaviourType::kDiscrete:
_engine->_animations->initAnim(AnimationTypes::kHide, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
break;
case HeroBehaviourType::kProtoPack:
case HeroBehaviourType::kMax:
break;
}
}
bool Movements::processAttackExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (!_engine->_gameState->_weapon) {
// Use Magic Ball
if (_engine->_gameState->hasItem(InventoryItems::kiMagicBall)) {
if (_engine->_gameState->_magicBall == -1) {
_engine->_animations->initAnim(AnimationTypes::kThrowBall, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
}
_lastJoyFlag = true;
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
return true;
}
} else if (_engine->_gameState->hasItem(InventoryItems::kiUseSabre)) {
if (actor->_genBody != BodyType::btSabre) {
_engine->_actor->initBody(BodyType::btSabre, actorIdx);
}
_engine->_animations->initAnim(AnimationTypes::kSabreAttack, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
_lastJoyFlag = true;
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
return true;
}
return false;
}
void Movements::processManualMovementExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->isAttackAnimationActive()) {
return;
}
if (actor->isJumpAnimationActive()) {
return;
}
if (actor->isAttackWeaponAnimationActive()) {
return;
}
if (!_changedCursorKeys || _actionNormal) {
// if walking should get stopped
if (!_engine->_input->isActionActive(TwinEActionType::MoveForward) && !_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
if (_lastJoyFlag && (_heroActionKey != _previousLoopActionKey || _changedCursorKeys != _previousChangedCursorKeys)) {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
}
_lastJoyFlag = false;
if (_engine->_input->isActionActive(TwinEActionType::MoveForward)) {
if (!_engine->_scene->_flagClimbing) {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
_engine->_animations->initAnim(AnimationTypes::kBackward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
_lastJoyFlag = true;
}
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
if (actor->_genAnim == AnimationTypes::kStanding) {
_engine->_animations->initAnim(AnimationTypes::kTurnLeft, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
} else {
if (!actor->_workFlags.bIsRotationByAnim) {
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
}
}
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
if (actor->_genAnim == AnimationTypes::kStanding) {
_engine->_animations->initAnim(AnimationTypes::kTurnRight, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
} else {
if (!actor->_workFlags.bIsRotationByAnim) {
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
}
}
_lastJoyFlag = true;
}
}
}
void Movements::processManualRotationExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (!_engine->_actor->_combatAuto && actor->isAttackAnimationActive()) {
// it is allowed to rotate in auto aggressive mode - but not in manual mode.
return;
}
if (actor->isJumpAnimationActive()) {
return;
}
int16 tempAngle;
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
tempAngle = LBAAngles::ANGLE_90;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
tempAngle = -LBAAngles::ANGLE_90;
} else {
tempAngle = LBAAngles::ANGLE_0;
}
initRealAngleConst(actor->_beta, actor->_beta + tempAngle, actor->_srot, &actor->realAngle);
}
void Movements::processManualAction(int actorIdx) {
if (IS_HERO(actorIdx)) {
_actionNormal = false;
if (_engine->_input->isHeroActionActive()) {
processBehaviourExecution(actorIdx);
} else if (_engine->_input->toggleActionIfActive(TwinEActionType::SpecialAction)) {
_actionNormal = true;
}
// MyFire & F_ALT
if (_engine->_input->isActionActive(TwinEActionType::ThrowMagicBall) && !_engine->_gameState->inventoryDisabled()) {
processAttackExecution(actorIdx);
}
}
processManualMovementExecution(actorIdx);
processManualRotationExecution(actorIdx);
}
void Movements::processFollowAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const ActorStruct *followedActor = _engine->_scene->getActor(actor->_followedActor);
int32 newAngle = getAngle(actor->posObj(), followedActor->posObj());
if (actor->_flags.bSprite3D) {
actor->_beta = newAngle;
} else {
initRealAngleConst(actor->_beta, newAngle, actor->_srot, &actor->realAngle);
}
}
void Movements::processRandomAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_workFlags.bIsRotationByAnim) {
return;
}
if (actor->brickCausesDamage()) {
const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90 + LBAAngles::ANGLE_180);
initRealAngleConst(actor->_beta, angle, actor->_srot, &actor->realAngle);
actor->_delayInMillis = _engine->timerRef + _engine->getRandomNumber(_engine->toSeconds(6)) + _engine->toSeconds(6);
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
if (!actor->realAngle.timeValue) {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
if (_engine->timerRef > actor->_delayInMillis) {
const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90);
initRealAngleConst(actor->_beta, angle, actor->_srot, &actor->realAngle);
actor->_delayInMillis = _engine->timerRef + _engine->getRandomNumber(_engine->toSeconds(6)) + _engine->toSeconds(6);
}
}
}
void Movements::processTrackAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_offsetTrack == -1) {
actor->_offsetTrack = 0;
}
}
void Movements::processSameXZAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const ActorStruct *followedActor = _engine->_scene->getActor(actor->_followedActor);
actor->_posObj.x = followedActor->_posObj.x;
actor->_posObj.z = followedActor->_posObj.z;
}
void Movements::manualRealAngle(ActorStruct *actor) {
int16 tempAngle = LBAAngles::ANGLE_0;
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
tempAngle = LBAAngles::ANGLE_90;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
tempAngle = -LBAAngles::ANGLE_90;
}
initRealAngleConst(actor->_beta, actor->_beta + tempAngle, actor->_srot, &actor->realAngle);
}
void Movements::doDir(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return;
}
if (actor->_workFlags.bIsFalling) {
if (actor->_move == ControlMode::kManual) {
manualRealAngle(actor);
// TODO: _lastJoyFlag = _joyFlag;
}
return;
}
if (!actor->_flags.bSprite3D && actor->_move != ControlMode::kManual) {
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
}
switch (actor->_move) {
case ControlMode::kManual:
processManualAction(actorIdx);
break;
case ControlMode::kFollow:
processFollowAction(actorIdx);
break;
case ControlMode::kRandom:
processRandomAction(actorIdx);
break;
case ControlMode::kTrack:
processTrackAction(actorIdx);
break;
case ControlMode::kSameXZ:
// TODO: see lSET_DIRMODE and lSET_DIRMODE_OBJ opcodes
processSameXZAction(actorIdx);
break;
/**
* The Actor's Track Script is stopped. Track Script execution may be started with Life Script of
* the Actor or other Actors (with SET_TRACK(_OBJ) command). This mode does not mean the Actor
* will literally not move, but rather that it's Track Script (also called Move Script) is
* initially stopped. The Actor may move if it is assigned a moving animation.
*/
case ControlMode::kNoMove:
case ControlMode::kFollow2: // unused
case ControlMode::kTrackAttack: // unused
break;
default:
warning("Unknown control mode %d", (int)actor->_move);
break;
}
}
} // namespace TwinE

View File

@@ -0,0 +1,201 @@
/* 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 TWINE_SCENE_MOVEMENTS_H
#define TWINE_SCENE_MOVEMENTS_H
#include "common/scummsys.h"
#include "twine/shared.h"
namespace TwinE {
class TwinEEngine;
class ActorStruct;
struct RealValue;
class Movements {
private:
TwinEEngine *_engine;
struct ChangedCursorKeys {
uint8 forwardChange = 0;
uint8 backwardChange = 0;
uint8 leftChange = 0;
uint8 rightChange = 0;
uint8 forwardDown = 0;
uint8 backwardDown = 0;
uint8 leftDown = 0;
uint8 rightDown = 0;
void update(TwinEEngine *engine);
inline bool operator==(const ChangedCursorKeys &rhs) const {
return forwardChange == rhs.forwardChange && backwardChange == rhs.backwardChange && leftChange == rhs.leftChange && rightChange == rhs.rightChange;
}
inline operator bool() const {
return forwardChange && backwardChange && leftChange && rightChange;
}
inline bool operator!=(const ChangedCursorKeys &rhs) const {
return forwardChange != rhs.forwardChange || backwardChange != rhs.backwardChange || leftChange != rhs.leftChange || rightChange != rhs.rightChange;
}
};
// enter, space, ...
int16 _heroActionKey = 0;
int32 _previousLoopActionKey = 0;
// cursor keys
ChangedCursorKeys _changedCursorKeys;
ChangedCursorKeys _previousChangedCursorKeys;
/**
* The Actor is controlled by the player. This works well only for the Hero Actor in general.
* To use it for other Actors they would have to have necessary animations that would be also
* correctly indexed. The primary purpose for this mode is to re-enable player's control over
* the Hero after it has been disabled for some reasons.
*/
void processManualAction(int actorIdx);
/**
* The Actor tries to move towards the target Actor. This only means that it will always face
* in its direction (as fast as the Rotation delay property allows). To make it really follow
* anything it must be assigned a moving Animation first, and the Actor will not stop by itself
* after reaching the target. To make a real following, the Actor's animation must be changed
* for example to standing animation when the Actor is near the target, and changed back to a
* moving animation when it's far from it. The Follow mode handles only the facing angle.
*/
void processFollowAction(int actorIdx);
/**
* Makes the Actor walk and turn by random angles and at random moments. In original game it is
* only used for Nitro-Mecha-Penguins, but it can be used for any 3-D Actor that has standing
* and walking animation (with virtual indexes 0 and 1 respectively). This mode requires the
* Randomize interval (Info1) property to be less or equal to 117, otherwise the Actor will just
* walk without turning. Exact meaning of the property is not known.
*/
void processRandomAction(int actorIdx);
/**
* The Actor's Track Script is run from the first command, and when it reaches END or STOP it
* starts over again.
*/
void processTrackAction(int actorIdx);
/**
* This mode is used to make an Actor follow specified Actor's X and Z (horizontal) coordinates.
* This is mainly used for Sprite Actors to be always above other Sprite Actors (like platforms).
* Unlike the Follow mode, this mode sets the Actor's position. If the Actor is a Sprite Actor,
* its speed is not taken into consideration in this mode.
*/
void processSameXZAction(int actorIdx);
void processBehaviourExecution(int actorIdx);
bool processAttackExecution(int actorIdx);
void processManualMovementExecution(int actorIdx);
void processManualRotationExecution(int actorIdx);
/**
* @return A value of @c true means that the actor should e.g. start reading a sign or checking
* a locker for loot or secrets or talking to an npc - this can get triggered by the SpecialAction binding
* in any behaviour mode
*/
bool _actionNormal = false;
void manualRealAngle(ActorStruct *actor);
public:
Movements(TwinEEngine *engine);
void setActionNormal(bool actionNormal);
void update();
/**
* Hero executes the current action of the trigger zone
*/
bool actionNormal() const;
bool _lastJoyFlag = false;
int32 _targetActorDistance = 0;
/**
* Get shadow position
* @param pos Shadow coordinates
*/
IVec3 getShadow(const IVec3 &pos);
/**
* Set actor safe angle
* @param startAngle start angle
* @param endAngle end angle
* @param stepAngle number of steps
* @param movePtr time pointer to update
*/
void initRealAngle(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr);
/**
* Clear actors safe angle
* @param actorPtr actor pointer
*/
void clearRealAngle(ActorStruct *actorPtr);
/**
* Set actor safe angle
* @param startAngle start angle
* @param endAngle end angle
* @param stepAngle number of steps
* @param movePtr time pointer to update
*/
void initRealValue(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr);
/**
* Get actor angle
* @param x1 Actor 1 X
* @param z1 Actor 1 Z
* @param x2 Actor 2 X
* @param z2 Actor 2 Z
*/
int32 getAngle(int32 x1, int32 z1, int32 x2, int32 z2);
inline int32 getAngle(const IVec3& v1, const IVec3 &v2) {
return getAngle(v1.x, v1.z, v2.x, v2.z);
}
/**
* Move actor around the scene
* @param start Current actor angle
* @param end Angle to rotate
* @param duration Rotate speed
* @param movePtr Pointer to process movements
*/
void initRealAngleConst(int32 start, int32 end, int32 duration, RealValue *movePtr) const;
void doDir(int32 actorIdx);
};
inline void Movements::setActionNormal(bool actionNormal) {
_actionNormal = actionNormal;
}
inline bool Movements::actionNormal() const {
return _actionNormal;
}
} // namespace TwinE
#endif

View File

@@ -0,0 +1,170 @@
/* 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 "twine/scene/rain.h"
#include "twine/renderer/renderer.h"
#include "twine/scene/grid.h"
#include "twine/twine.h"
namespace TwinE {
#define RAIN_VX 200
#define RAIN_VY 2500
#define RAIN_WEIGHT 30
#define RAIN_STOP 0
#define RAIN_DELTA_X 128
#define RAIN_DELTA_Y 256
#define RAIN_DELTA_Z 128
void Rain::InitOneRain(T_RAIN *pt) {
IVec3 cameraPos = _engine->_renderer->getCameraPosition();
int32 rndy = _engine->getRandomNumber(cameraPos.y + 10000);
pt->YRain = cameraPos.y + rndy;
rndy = rndy / 2 + 15000;
pt->XRain = cameraPos.x - rndy + _engine->getRandomNumber(30000);
pt->ZRain = cameraPos.z - rndy + _engine->getRandomNumber(30000);
pt->Timer = 0;
}
void Rain::InitRain() {
for (int32 i = 0; i < MAX_RAIN; i++) {
InitOneRain(&TabRain[i]);
}
LastTimer = 0;
}
void Rain::GereRain() {
int32 temp = _engine->timerRef;
DeltaRain = LastTimer ? (temp - LastTimer) * 10 : 0;
LastTimer = temp;
for (int32 i = 0; i < MAX_RAIN; i++) {
if (!TabRain[i].Timer) {
TabRain[i].XRain += DeltaRain / 2;
TabRain[i].ZRain += DeltaRain / 2;
TabRain[i].YRain -= DeltaRain;
}
}
}
void Rain::ClearImpactRain() {
for (int32 i = 0; i < MAX_RAIN; i++) {
if (TabRain[i].Timer) {
InitOneRain(&TabRain[i]);
}
}
}
void Rain::AffRain() {
int32 lFactorX = _engine->_renderer->getLFactorX();
int32 lFactorY = _engine->_renderer->getLFactorY();
IVec3 cameraRot = _engine->_renderer->getCameraRotation();
int32 cameraZr = cameraRot.z;
// ClipZFar approximation
int32 clipZFar = 14000; // Default value from CREDITS.CPP
int32 startZFog = 5000; // Default value from CREDITS.CPP
for (int32 i = 0; i < MAX_RAIN; i++) {
if (TabRain[i].Timer) {
int32 dt = LastTimer - TabRain[i].Timer;
int32 c = TabRain[i].XRain >> 16;
int32 x = (int16)(TabRain[i].XRain & 0xFFFF);
int32 y = TabRain[i].YRain;
int32 z = TabRain[i].ZRain;
int32 xp, yp;
yp = (RAIN_VY - RAIN_WEIGHT * dt) * dt / 256;
if (yp < 0) {
yp = 0;
xp = RAIN_VX * RAIN_VY / RAIN_WEIGHT / 256;
} else {
xp = RAIN_VX * dt / 256;
yp = (yp * lFactorY) / z;
}
xp = (xp * lFactorX) / z;
int32 x0 = x - xp;
int32 x1 = x + xp;
// int32 y0 = y - yp;
// int32 y1 = y;
// z = ruleThree32(0, 65535, clipZFar, z);
// Draw splash
_engine->_workVideoBuffer.drawLine(x, y, x0, y - yp, c);
_engine->_workVideoBuffer.drawLine(x, y, x1, y - yp, c);
if (dt && !yp) {
InitOneRain(&TabRain[i]);
}
} else {
if (TabRain[i].YRain <= RAIN_STOP) {
InitOneRain(&TabRain[i]);
continue;
}
IVec3 p1 = _engine->_renderer->longWorldRot(TabRain[i].XRain - RAIN_DELTA_X, TabRain[i].YRain + RAIN_DELTA_Y, TabRain[i].ZRain - RAIN_DELTA_Z);
IVec3 proj1 = _engine->_renderer->projectPoint(p1);
int32 xp = proj1.x;
int32 yp = proj1.y;
int32 z0 = ruleThree32(0, 65535, clipZFar, cameraZr - p1.z);
IVec3 p2 = _engine->_renderer->longWorldRot(TabRain[i].XRain, TabRain[i].YRain, TabRain[i].ZRain);
IVec3 proj2 = _engine->_renderer->projectPoint(p2);
int32 Z0 = cameraZr - p2.z;
// int32 z1 = ruleThree32(0, 65535, clipZFar, Z0);
int32 c = boundRuleThree(16 * 3 + 10, 16 * 3 + 3, clipZFar - startZFog, Z0);
// Draw rain drop
_engine->_workVideoBuffer.drawLine(xp, yp, proj2.x, proj2.y, c);
// Check collision with ground
int32 groundHeight = 0;
IVec3 pos(TabRain[i].XRain, TabRain[i].YRain, TabRain[i].ZRain);
_engine->_grid->getBlockBufferGround(pos, groundHeight);
if (TabRain[i].YRain <= groundHeight) {
// Splash
TabRain[i].XRain = ((xp & 0xFFFF) | (c << 16));
TabRain[i].YRain = yp;
TabRain[i].ZRain = z0;
TabRain[i].Timer = LastTimer;
}
}
}
}
Rain::Rain(TwinEEngine *engine) : _engine(engine) {
}
} // namespace TwinE

View File

@@ -0,0 +1,59 @@
/* 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 TWINE_SCENE_RAIN_H
#define TWINE_SCENE_RAIN_H
#include "twine/scene/actor.h"
#include "twine/twine.h"
namespace TwinE {
#define MAX_RAIN 200
class Rain {
private:
TwinEEngine *_engine;
int32 LastTimer = 0;
int32 DeltaRain = 0;
public:
struct T_RAIN {
int32 XRain = 0;
int32 YRain = 0;
int32 ZRain = 0;
int32 Timer = 0;
};
T_RAIN TabRain[MAX_RAIN];
Rain(TwinEEngine *engine);
void InitOneRain(T_RAIN *pt);
void InitRain();
void GereRain();
void ClearImpactRain();
void AffRain();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,885 @@
/* 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 "twine/scene/scene.h"
#include "twine/scene/rain.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "engines/enhancements.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_state.h"
#include "twine/holomap.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/extra.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Scene::~Scene() {
free(_currentScene);
}
void Scene::setActorStaticFlags(ActorStruct *act, uint32 staticFlags) {
if (staticFlags & 0x1) {
act->_flags.bComputeCollisionWithObj = 1;
}
if (staticFlags & 0x2) {
act->_flags.bComputeCollisionWithBricks = 1;
}
if (staticFlags & 0x4) {
act->_flags.bCheckZone = 1;
}
if (staticFlags & 0x8) {
act->_flags.bSpriteClip = 1;
}
if (staticFlags & 0x10) {
act->_flags.bCanBePushed = 1;
}
if (staticFlags & 0x20) {
act->_flags.bComputeLowCollision = 1;
}
if (staticFlags & 0x40) {
act->_flags.bCanDrown = 1;
}
if (staticFlags & 0x80) {
act->_flags.bComputeCollisionWithFloor = 1;
}
if (staticFlags & 0x100) {
act->_flags.bUnk0100 = 1;
}
if (staticFlags & 0x200) {
act->_flags.bIsInvisible = 1;
}
if (staticFlags & 0x400) {
act->_flags.bSprite3D = 1;
}
if (staticFlags & 0x800) {
act->_flags.bObjFallable = 1;
}
if (staticFlags & 0x1000) {
act->_flags.bNoShadow = 1;
}
if (staticFlags & 0x2000) {
act->_flags.bIsBackgrounded = 1;
}
if (staticFlags & 0x4000) {
act->_flags.bIsCarrierActor = 1;
}
if (staticFlags & 0x8000) {
act->_flags.bUseMiniZv = 1;
}
if (staticFlags & 0x10000) {
act->_flags.bHasInvalidPosition = 1;
}
if (staticFlags & 0x20000) {
act->_flags.bNoElectricShock = 1;
}
if (staticFlags & 0x40000) {
act->_flags.bHasSpriteAnim3D = 1;
}
if (staticFlags & 0x80000) {
act->_flags.bNoPreClipping = 1;
}
if (staticFlags & 0x100000) {
act->_flags.bHasZBuffer = 1;
}
if (staticFlags & 0x200000) {
act->_flags.bHasZBufferInWater = 1;
}
}
void Scene::setBonusParameterFlags(ActorStruct *act, uint16 bonusFlags) {
if (bonusFlags & 0x1) {
act->_bonusParameter.givenNothing = 1;
}
if (bonusFlags & 0x2) {
act->_bonusParameter.unk2 = 1;
}
if (bonusFlags & 0x4) {
act->_bonusParameter.unk3 = 1;
}
if (bonusFlags & 0x8) {
act->_bonusParameter.unk4 = 1;
}
if (bonusFlags & 0x10) {
act->_bonusParameter.kashes = 1;
}
if (bonusFlags & 0x20) {
act->_bonusParameter.lifepoints = 1;
}
if (bonusFlags & 0x40) {
act->_bonusParameter.magicpoints = 1;
}
if (bonusFlags & 0x80) {
act->_bonusParameter.key = 1;
}
if (bonusFlags & 0x100) {
act->_bonusParameter.cloverleaf = 1;
}
}
bool Scene::loadSceneCubeXY(int numcube, int32 *cubex, int32 *cubey) {
uint8 *scene = nullptr;
// numcube+1 because at 0 is SizeCube.MAX (size of the largest .SCC)
const int32 sceneSize = HQR::getAllocEntry(&scene, Resources::HQR_SCENE_FILE, numcube + 1);
if (sceneSize <= 0) {
return false;
}
Common::MemoryReadStream stream(scene, sceneSize, DisposeAfterUse::YES);
*cubex = *cubey = 0;
// World info: INFO_WORLD
const uint8 island = stream.readByte();
// Used only for 3DExt
const int32 x = stream.readByte();
const int32 y = stream.readByte();
/*uint8 shadowlvl =*/stream.readByte();
/*uint8 modelaby =*/stream.readByte();
const uint8 cubemode = stream.readByte();
if (cubemode == CUBE_EXTERIEUR && island == _island && ABS(x - _currentCubeX) <= 1 && ABS(y - _currentCubeY) <= 1) {
*cubex = x;
*cubey = y;
return true;
}
return false;
}
void Scene::loadModel(ActorStruct &actor, int32 modelIndex, bool lba1) {
actor._body = modelIndex;
if (!actor._flags.bSprite3D) {
debug(1, "Init actor with model %i", modelIndex);
_engine->_resources->loadEntityData(actor._entityData, modelIndex);
actor._entityDataPtr = &actor._entityData;
} else {
actor._entityDataPtr = nullptr;
}
}
bool Scene::loadSceneLBA2() {
Common::MemoryReadStream stream(_currentScene, _currentSceneSize);
_island = stream.readByte();
_sceneTextBank = (TextBankId)_island;
_currentCubeX = stream.readByte();
_currentCubeY = stream.readByte();
_shadowLevel = stream.readByte();
_modeLabyrinthe = stream.readByte();
_isOutsideScene = stream.readByte();
/*uint8 n =*/ stream.readByte();
_alphaLight = ClampAngle(stream.readSint16LE());
_betaLight = ClampAngle(stream.readSint16LE());
debug(2, "Using %i and %i as light vectors", _alphaLight, _betaLight);
for (int i = 0; i < 4; ++i) {
_sampleAmbiance[i] = stream.readUint16LE();
_sampleRepeat[i] = stream.readUint16LE();
_sampleRound[i] = stream.readUint16LE();
_sampleFrequency[i] = stream.readUint16LE();
_sampleVolume[i] = stream.readUint16LE();
}
_sampleMinDelay = stream.readUint16LE();
_sampleMinDelayRnd = stream.readUint16LE();
_cubeJingle = stream.readByte();
// load hero properties
_sceneHeroPos.x = stream.readSint16LE();
_sceneHeroPos.y = stream.readSint16LE();
_sceneHeroPos.z = stream.readSint16LE();
_sceneHero->_moveScriptSize = (int16)stream.readUint16LE();
_sceneHero->_ptrTrack = _currentScene + stream.pos();
stream.skip(_sceneHero->_moveScriptSize);
_sceneHero->_lifeScriptSize = (int16)stream.readUint16LE();
_sceneHero->_lifeScript = _currentScene + stream.pos();
stream.skip(_sceneHero->_lifeScriptSize);
_nbObjets = (int16)stream.readUint16LE();
int cnt = 1;
for (int32 a = 1; a < _nbObjets; a++, cnt++) {
_engine->_actor->initObject(a);
ActorStruct *act = &_sceneActors[a];
setActorStaticFlags(act, stream.readUint32LE());
loadModel(*act, (int16)stream.readUint16LE(), false);
act->_genBody = (BodyType)stream.readByte();
act->_genAnim = (AnimationTypes)stream.readSint16LE();
act->_sprite = (int16)stream.readUint16LE();
act->_posObj.x = (int16)stream.readUint16LE();
act->_posObj.y = (int16)stream.readUint16LE();
act->_posObj.z = (int16)stream.readUint16LE();
act->_oldPos = act->posObj();
act->_hitForce = stream.readByte();
setBonusParameterFlags(act, stream.readUint16LE());
act->_beta = (int16)stream.readUint16LE();
act->_srot = (int16)stream.readUint16LE();
act->_move = (ControlMode)stream.readByte(); // move
act->_cropLeft = stream.readSint16LE();
act->_delayInMillis = act->_cropLeft; // TODO: this might not be needed
act->_cropTop = stream.readSint16LE();
act->_cropRight = stream.readSint16LE();
act->_cropBottom = stream.readSint16LE();
act->_followedActor = act->_cropBottom; // TODO: is this needed? and valid?
act->_bonusAmount = stream.readSint16LE();
act->_talkColor = stream.readByte();
if (act->_flags.bHasSpriteAnim3D) {
/*act->spriteAnim3DNumber = */stream.readSint32LE();
/*act->spriteSizeHit = */stream.readSint16LE();
/*act->cropBottom = act->spriteSizeHit;*/
}
act->_armor = stream.readByte();
act->setLife(stream.readByte());
act->_moveScriptSize = (int16)stream.readUint16LE();
act->_ptrTrack = _currentScene + stream.pos();
stream.skip(act->_moveScriptSize);
act->_lifeScriptSize = (int16)stream.readUint16LE();
act->_lifeScript = _currentScene + stream.pos();
stream.skip(act->_lifeScriptSize);
if (_engine->_debugState->_onlyLoadActor != -1 && _engine->_debugState->_onlyLoadActor != cnt) {
_nbObjets--;
a--;
}
}
/* uint32 checksum = */stream.readUint32LE();
_sceneNumZones = (int16)stream.readUint16LE();
for (int32 i = 0; i < _sceneNumZones; i++) {
ZoneStruct *zone = &_sceneZones[i];
zone->mins.x = stream.readSint32LE();
zone->mins.y = stream.readSint32LE();
zone->mins.z = stream.readSint32LE();
zone->maxs.x = stream.readSint32LE();
zone->maxs.y = stream.readSint32LE();
zone->maxs.z = stream.readSint32LE();
zone->infoData.generic.info0 = stream.readSint32LE();
zone->infoData.generic.info1 = stream.readSint32LE();
zone->infoData.generic.info2 = stream.readSint32LE();
zone->infoData.generic.info3 = stream.readSint32LE();
zone->infoData.generic.info4 = stream.readSint32LE();
zone->infoData.generic.info5 = stream.readSint32LE();
zone->infoData.generic.info6 = stream.readSint32LE();
zone->infoData.generic.info7 = stream.readSint32LE();
zone->type = (ZoneType)stream.readUint16LE();
zone->num = stream.readSint16LE();
}
_sceneNumTracks = (int16)stream.readUint16LE();
for (int32 i = 0; i < _sceneNumTracks; i++) {
IVec3 *point = &_sceneTracks[i];
point->x = stream.readSint32LE();
point->y = stream.readSint32LE();
point->z = stream.readSint32LE();
}
uint16 sceneNumPatches = stream.readUint32LE();
for (uint16 i = 0; i < sceneNumPatches; i++) {
/*size = */stream.readUint16LE();
/*offset = */stream.readUint16LE();
}
return true;
}
// LoadScene
bool Scene::loadSceneLBA1() {
Common::MemoryReadStream stream(_currentScene, _currentSceneSize);
// load scene ambience properties
_sceneTextBank = (TextBankId)stream.readByte();
_currentGameOverScene = stream.readByte();
stream.skip(4);
// FIXME: Workaround to fix lighting issue - not using proper dark light
// Using 1215 and 1087 as light vectors - scene 8
_alphaLight = ClampAngle((int16)stream.readUint16LE());
_betaLight = ClampAngle((int16)stream.readUint16LE());
debug(2, "Using %i and %i as light vectors", _alphaLight, _betaLight);
for (int i = 0; i < 4; ++i) {
_sampleAmbiance[i] = stream.readUint16LE();
_sampleRepeat[i] = stream.readUint16LE();
_sampleRound[i] = stream.readUint16LE();
}
_sampleMinDelay = stream.readUint16LE();
_sampleMinDelayRnd = stream.readUint16LE();
_cubeJingle = stream.readByte();
// load hero properties
_sceneHeroPos.x = (int16)stream.readUint16LE();
_sceneHeroPos.y = (int16)stream.readUint16LE();
_sceneHeroPos.z = (int16)stream.readUint16LE();
_sceneHero->_moveScriptSize = (int16)stream.readUint16LE();
_sceneHero->_ptrTrack = _currentScene + stream.pos();
stream.skip(_sceneHero->_moveScriptSize);
_sceneHero->_lifeScriptSize = (int16)stream.readUint16LE();
_sceneHero->_lifeScript = _currentScene + stream.pos();
stream.skip(_sceneHero->_lifeScriptSize);
_nbObjets = (int16)stream.readUint16LE();
int cnt = 1;
for (int32 a = 1; a < _nbObjets; a++, cnt++) {
_engine->_actor->initObject(a);
ActorStruct *act = &_sceneActors[a];
setActorStaticFlags(act, stream.readUint16LE());
loadModel(*act, stream.readUint16LE(), true);
act->_genBody = (BodyType)stream.readByte();
act->_genAnim = (AnimationTypes)stream.readByte();
act->_sprite = (int16)stream.readUint16LE();
act->_posObj.x = (int16)stream.readUint16LE();
act->_posObj.y = (int16)stream.readUint16LE();
act->_posObj.z = (int16)stream.readUint16LE();
act->_oldPos = act->posObj();
act->_hitForce = stream.readByte();
setBonusParameterFlags(act, stream.readUint16LE());
act->_bonusParameter.givenNothing = 0;
act->_beta = (int16)stream.readUint16LE();
act->_srot = (int16)stream.readUint16LE();
act->_move = (ControlMode)stream.readUint16LE();
act->_cropLeft = stream.readSint16LE();
act->_delayInMillis = act->_cropLeft; // TODO: this might not be needed
act->_cropTop = stream.readSint16LE();
act->_cropRight = stream.readSint16LE();
act->_cropBottom = stream.readSint16LE();
act->_followedActor = act->_cropBottom; // TODO: is this needed? and valid?
act->_bonusAmount = stream.readByte();
act->_talkColor = stream.readByte();
act->_armor = stream.readByte();
act->setLife(stream.readByte());
act->_moveScriptSize = (int16)stream.readUint16LE();
act->_ptrTrack = _currentScene + stream.pos();
stream.skip(act->_moveScriptSize);
act->_lifeScriptSize = (int16)stream.readUint16LE();
act->_lifeScript = _currentScene + stream.pos();
stream.skip(act->_lifeScriptSize);
if (_engine->_debugState->_onlyLoadActor != -1 && _engine->_debugState->_onlyLoadActor != cnt) {
_nbObjets--;
a--;
}
}
_sceneNumZones = (int16)stream.readUint16LE();
for (int32 i = 0; i < _sceneNumZones; i++) {
ZoneStruct *zone = &_sceneZones[i];
zone->mins.x = stream.readSint16LE();
zone->mins.y = stream.readSint16LE();
zone->mins.z = stream.readSint16LE();
zone->maxs.x = stream.readSint16LE();
zone->maxs.y = stream.readSint16LE();
zone->maxs.z = stream.readSint16LE();
zone->type = (ZoneType)stream.readUint16LE();
zone->num = stream.readSint16LE();
zone->infoData.generic.info0 = stream.readSint16LE();
zone->infoData.generic.info1 = stream.readSint16LE();
zone->infoData.generic.info2 = stream.readSint16LE();
zone->infoData.generic.info3 = stream.readSint16LE();
}
_sceneNumTracks = stream.readUint16LE();
for (int32 i = 0; i < _sceneNumTracks; i++) {
IVec3 *point = &_sceneTracks[i];
point->x = stream.readSint16LE();
point->y = stream.readSint16LE();
point->z = stream.readSint16LE();
}
if (_engine->isLBA1()) {
if (_engine->enhancementEnabled(kEnhMinorBugFixes)) {
if (_numCube == LBA1SceneId::Hamalayi_Mountains_landing_place) {
// move the mine a little bit, as it's too close to the change cube zone
_sceneActors[21]._posObj.x = _sceneActors[21]._oldPos.x = 6656 + 256;
_sceneActors[21]._posObj.z = _sceneActors[21]._oldPos.z = 768;
}
#if 0
else if (_numCube == LBA1SceneId::Tippet_Island_Secret_passage_scene_1) {
_sceneZones[6].maxs.z = 3616;
}
#endif
}
if (_engine->enhancementEnabled(kEnhGameBreakingBugFixes)) {
if (_numCube == LBA1SceneId::Principal_Island_outside_the_fortress) {
// https://bugs.scummvm.org/ticket/13818
_sceneActors[29]._posObj.z = _sceneActors[29]._oldPos.z = 1795;
} else if (_numCube == LBA1SceneId::Principal_Island_inside_the_fortress) {
// https://bugs.scummvm.org/ticket/13819
// Set this zone to something invalid to fix a getting-stuck-bug
// the original value was ZoneType::kGrid (3)
_sceneZones[11].type = ZoneType::kFunFrockFix;
}
}
}
return true;
}
bool Scene::loadScene(int32 index) {
// load scene from file
if (_engine->isLBA2()) {
index++;
}
_currentSceneSize = HQR::getAllocEntry(&_currentScene, Resources::HQR_SCENE_FILE, index);
if (_currentSceneSize == 0) {
return false;
}
if (_engine->isLBA1()) {
return loadSceneLBA1();
} else if (_engine->isLBA2()) {
_engine->_rain->InitRain();
return loadSceneLBA2();
}
return false;
}
void Scene::clearScene() {
_engine->_extra->clearExtra();
// ClearFlagsCube
for (int32 i = 0; i < ARRAYSIZE(_listFlagCube); i++) {
_listFlagCube[i] = 0;
}
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
_engine->_redraw->overlayList[i].num = -1;
}
_engine->_screens->_flagPalettePcx = false;
}
void Scene::reloadCurrentScene() {
_newCube = _numCube;
}
void Scene::dumpSceneScript(const char *type, int actorIdx, const uint8* script, int size) const {
Common::String fname = Common::String::format("./dumps/%i-%i.%s", _numCube, actorIdx, type);
Common::DumpFile out;
if (!out.open(fname.c_str(), true)) {
warning("Scene::dumpSceneScript(): Can not open dump file %s", fname.c_str());
} else {
out.write(script, size);
out.flush();
out.close();
}
}
void Scene::dumpSceneScripts() const {
for (int32 a = 0; a < _nbObjets; ++a) {
const ActorStruct &actor = _sceneActors[a];
dumpSceneScript("life", a, actor._lifeScript, actor._lifeScriptSize);
dumpSceneScript("move", a, actor._ptrTrack, actor._moveScriptSize);
}
}
void Scene::changeCube() {
if (_engine->isLBA1()) {
if (_engine->enhancementEnabled(kEnhMinorBugFixes)) {
if (_numCube == LBA1SceneId::Citadel_Island_Harbor && _newCube == LBA1SceneId::Principal_Island_Harbor) {
if (_sceneNumZones >= 15 && _sceneNumTracks >= 8) {
const ZoneStruct *zone = &_sceneZones[15];
const IVec3 &track = _sceneTracks[8];
IVec3 &pos = _zoneHeroPos;
pos.x = zone->infoData.ChangeScene.x - zone->mins.x + track.x;
pos.y = zone->infoData.ChangeScene.y - zone->mins.y + track.y;
pos.z = zone->infoData.ChangeScene.z - zone->mins.z + track.z;
_engine->_scene->_flagChgCube = ScenePositionType::kZone;
debug(2, "Using zone position %i:%i:%i", pos.x, pos.y, pos.z);
}
}
}
// change twinsen house destroyed hard-coded
if (_newCube == LBA1SceneId::Citadel_Island_near_twinsens_house && _engine->_gameState->hasOpenedFunfrocksSafe()) {
_newCube = LBA1SceneId::Citadel_Island_Twinsens_house_destroyed;
}
}
// local backup previous scene
_oldcube = _numCube;
_numCube = _newCube;
snprintf(_engine->_gameState->_sceneName, sizeof(_engine->_gameState->_sceneName), "%i %s", _numCube, _engine->_holomap->getLocationName(_numCube));
debug(2, "Entering scene %s (came from %i)", _engine->_gameState->_sceneName, _oldcube);
if (_engine->isLBA1()) {
if (_newCube == LBA1SceneId::Polar_Island_end_scene) {
_engine->unlockAchievement("LBA_ACH_001");
// if you finish the game in less than 4 hours
if (_engine->getTotalPlayTime() <= 1000 * 60 * 60 * 4) {
_engine->unlockAchievement("LBA_ACH_005");
}
} else if (_newCube == LBA1SceneId::Brundle_Island_Secret_room) {
_engine->unlockAchievement("LBA_ACH_006");
}
}
_engine->_sound->stopSamples();
clearScene();
_engine->_actor->loadHeroEntities();
_sceneHero->_move = ControlMode::kManual;
_sceneHero->_zoneSce = -1;
_sceneHero->_offsetLife = 0;
_sceneHero->_offsetTrack = -1;
_sceneHero->_labelTrack = -1;
loadScene(_newCube);
if (ConfMan.getBool("dump_scripts")) {
dumpSceneScripts();
}
if (_numHolomapTraj != -1) {
_engine->testRestoreModeSVGA(false);
_engine->_screens->setBlackPal();
_engine->_holomap->holoTraj(_numHolomapTraj);
_numHolomapTraj = -1;
_engine->_screens->_flagFade = true;
} else {
// TODO lbawin can do a fade here (if activated)
// _engine->_screens->_flagFade = true;
}
if (_newCube == LBA1SceneId::Citadel_Island_end_sequence_1 || _newCube == LBA1SceneId::Citadel_Island_end_sequence_2) {
_sceneTextBank = TextBankId::Tippet_Island;
}
_engine->_text->initSceneTextBank();
if (_cubeJingle != 255) {
// _engine->_music->fadeMusicMidi(1);
}
_engine->_grid->initGrid(_newCube);
if (_flagChgCube == ScenePositionType::kZone) {
_sceneStart = _zoneHeroPos;
} else if (_flagChgCube == ScenePositionType::kScene || _flagChgCube == ScenePositionType::kNoPosition) {
_sceneStart = _sceneHeroPos;
}
_sceneHero->_posObj = _sceneStart;
_startYFalling = _sceneStart.y;
_engine->_renderer->setLightVector(_alphaLight, _betaLight, LBAAngles::ANGLE_0);
if (_oldcube != SCENE_CEILING_GRID_FADE_1 && _oldcube != _newCube) {
_engine->_actor->_previousHeroBehaviour = _engine->_actor->_heroBehaviour;
_engine->_actor->_previousHeroAngle = _sceneHero->_beta;
_engine->autoSave();
}
_engine->_actor->restartPerso();
// StartInitAllObjs
for (int32 a = 1; a < _nbObjets; a++) {
_engine->_actor->startInitObj(a);
}
_engine->_gameState->_nbLittleKeys = 0;
_engine->_gameState->_magicBall = -1;
_engine->_movements->_lastJoyFlag = true;
_engine->_grid->_zoneGrm = -1;
_engine->_grid->_indexGrm = -1;
_engine->_redraw->_firstTime = true;
_engine->_cameraZone = false;
_newCube = SCENE_CEILING_GRID_FADE_1;
_flagChgCube = ScenePositionType::kNoPosition;
_flagRenderGrid = true;
_samplePlayed = 2 * 4 * 8;
_timerNextAmbiance = 0;
ActorStruct *followedActor = getActor(_numObjFollow);
_engine->_grid->centerOnActor(followedActor);
_engine->_screens->_flagFade = true;
_engine->_renderer->setLightVector(_alphaLight, _betaLight, LBAAngles::ANGLE_0);
_zoneHeroPos = IVec3();
debug(2, "Scene %i music track id: %i", _numCube, _cubeJingle);
if (_cubeJingle != 255) {
_engine->_music->playMusic(_cubeJingle);
}
_engine->_gameState->handleLateGameItems();
}
ActorStruct *Scene::getActor(int32 actorIdx) {
if (actorIdx < 0 || actorIdx >= NUM_MAX_ACTORS) {
error("Invalid actor id given: %i", actorIdx);
}
return &_sceneActors[actorIdx];
}
void Scene::initSceneVars() {
_sampleAmbiance[0] = -1;
_sampleAmbiance[1] = -1;
_sampleAmbiance[2] = -1;
_sampleAmbiance[3] = -1;
_sampleRepeat[0] = 0;
_sampleRepeat[1] = 0;
_sampleRepeat[2] = 0;
_sampleRepeat[3] = 0;
_sampleRound[0] = 0;
_sampleRound[1] = 0;
_sampleRound[2] = 0;
_sampleRound[3] = 0;
_nbObjets = 0;
_sceneNumZones = 0;
_sceneNumTracks = 0;
}
void Scene::playSceneMusic() {
if (_engine->isLBA1()) {
if (_numCube == LBA1SceneId::Tippet_Island_Twinsun_Cafe && _engine->_gameState->hasArrivedHamalayi()) {
if (_engine->isCDROM()) {
_engine->_music->playCdTrack(8);
} else {
_engine->_music->playMusic(_cubeJingle);
}
return;
}
}
_engine->_music->playMidiFile(_cubeJingle);
}
void Scene::processEnvironmentSound() {
if (_engine->timerRef < _timerNextAmbiance) {
return;
}
int16 currentAmb = _engine->getRandomNumber(4); // random ambiance
for (int32 s = 0; s < 4; s++) {
if (!(_samplePlayed & (1 << currentAmb))) { // if not already played
_samplePlayed |= (1 << currentAmb); // make sample played
if (_samplePlayed == 15) { // reset if all samples played
_samplePlayed = 0;
}
const int16 sampleIdx = _sampleAmbiance[currentAmb];
if (sampleIdx != -1) {
int16 decal = _sampleRound[currentAmb];
int16 repeat = _sampleRepeat[currentAmb];
const uint16 pitchbend = 0x1000 + _engine->getRandomNumber(decal) - (decal / 2);
_engine->_sound->mixSample(sampleIdx, pitchbend, repeat, 110, 110);
break;
}
}
currentAmb++; // try next ambiance
currentAmb &= 3; // loop in all 4 ambiances
}
// compute next ambiance timer
_timerNextAmbiance = _engine->timerRef + _engine->toSeconds(_engine->getRandomNumber(_sampleMinDelayRnd) + _sampleMinDelay);
}
void Scene::processZoneExtraBonus(ZoneStruct *zone) {
if (zone->infoData.Bonus.used) {
return;
}
const int bonusSprite = _engine->_extra->getBonusSprite(zone->infoData.Bonus.typesFlag);
if (bonusSprite == -1) {
return;
}
const int32 amount = zone->infoData.Bonus.amount;
const int32 x = (zone->maxs.x + zone->mins.x) / 2;
const int32 z = (zone->maxs.z + zone->mins.z) / 2;
const int32 angle = _engine->_movements->getAngle(x, z, _sceneHero->_posObj.x, _sceneHero->_posObj.z);
const int32 index = _engine->_extra->addExtraBonus(x, zone->maxs.y, z, LBAAngles::ANGLE_63, angle, bonusSprite, amount);
if (index != -1) {
_engine->_extra->_extraList[index].type |= ExtraType::TIME_IN;
zone->infoData.Bonus.used = 1; // set as used
}
}
void Scene::checkZoneSce(int32 actorIdx) {
ActorStruct *actor = &_sceneActors[actorIdx];
int32 currentX = actor->_posObj.x;
int32 currentY = actor->_posObj.y;
int32 currentZ = actor->_posObj.z;
actor->_zoneSce = -1;
bool flaggrm = false;
if (IS_HERO(actorIdx)) {
_flagClimbing = false;
}
for (int32 z = 0; z < _sceneNumZones; z++) {
ZoneStruct *zone = &_sceneZones[z];
// check if actor is in zone
if ((currentX >= zone->mins.x && currentX <= zone->maxs.x) &&
(currentY >= zone->mins.y && currentY <= zone->maxs.y) &&
(currentZ >= zone->mins.z && currentZ <= zone->maxs.z)) {
switch (zone->type) {
default:
warning("lba2 zone types not yet implemented");
break;
case ZoneType::kFunFrockFix:
break;
case ZoneType::kCube:
if (IS_HERO(actorIdx) && actor->_lifePoint > 0) {
_newCube = zone->num;
_zoneHeroPos.x = actor->_posObj.x - zone->mins.x + zone->infoData.ChangeScene.x;
_zoneHeroPos.y = actor->_posObj.y - zone->mins.y + zone->infoData.ChangeScene.y;
_zoneHeroPos.z = actor->_posObj.z - zone->mins.z + zone->infoData.ChangeScene.z;
_flagChgCube = ScenePositionType::kZone;
}
break;
case ZoneType::kCamera:
if (_numObjFollow == actorIdx && !_engine->_debugState->_useFreeCamera) {
_engine->_cameraZone = true;
if (_engine->_grid->_startCube.x != zone->infoData.CameraView.x || _engine->_grid->_startCube.y != zone->infoData.CameraView.y || _engine->_grid->_startCube.z != zone->infoData.CameraView.z) {
_engine->_grid->_startCube.x = zone->infoData.CameraView.x;
_engine->_grid->_startCube.y = zone->infoData.CameraView.y;
_engine->_grid->_startCube.z = zone->infoData.CameraView.z;
_engine->_redraw->_firstTime = true;
}
}
break;
case ZoneType::kSceneric:
actor->_zoneSce = zone->num;
break;
case ZoneType::kGrid:
if (_numObjFollow == actorIdx) {
flaggrm = true;
if (_engine->_grid->_zoneGrm != zone->num) {
if (_engine->_grid->_zoneGrm != -1) {
_engine->_grid->copyMapToCube();
}
_engine->_grid->_zoneGrm = zone->num;
_engine->_grid->_indexGrm = z;
_engine->saveTimer(false);
_engine->_grid->initCellingGrid(_engine->_grid->_zoneGrm);
_engine->restoreTimer();
}
}
break;
case ZoneType::kObject:
if (IS_HERO(actorIdx) && _engine->_movements->actionNormal()) {
_engine->_animations->initAnim(AnimationTypes::kAction, AnimType::kAnimationThen, AnimationTypes::kStanding, OWN_ACTOR_SCENE_INDEX);
processZoneExtraBonus(zone);
}
break;
case ZoneType::kText:
if (IS_HERO(actorIdx) && _engine->_movements->actionNormal()) {
_engine->saveTimer(false);
_engine->testRestoreModeSVGA(true);
_engine->_text->setFontCrossColor(zone->infoData.DisplayText.textColor);
_talkingActor = actorIdx;
_engine->_text->drawTextProgressive((TextId)zone->num);
_engine->restoreTimer();
_engine->_redraw->drawScene(true);
}
break;
case ZoneType::kLadder:
if (IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour != HeroBehaviourType::kProtoPack && (actor->_genAnim == AnimationTypes::kForward || actor->_genAnim == AnimationTypes::kTopLadder || actor->_genAnim == AnimationTypes::kClimbLadder)) {
IVec2 destPos = _engine->_renderer->rotate(actor->_boundingBox.mins.x, actor->_boundingBox.mins.z, actor->_beta + LBAAngles::ANGLE_360 + LBAAngles::ANGLE_135);
destPos.x += actor->_processActor.x;
destPos.y += actor->_processActor.z;
if (destPos.x >= 0 && destPos.y >= 0 && destPos.x <= SCENE_SIZE_MAX && destPos.y <= SCENE_SIZE_MAX) {
if (_engine->_grid->worldColBrick(destPos.x, actor->_posObj.y + SIZE_BRICK_Y, destPos.y) != ShapeType::kNone) {
_flagClimbing = true;
if (actor->_posObj.y >= (zone->mins.y + zone->maxs.y) / 2) {
_engine->_animations->initAnim(AnimationTypes::kTopLadder, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx); // reached end of ladder
} else {
_engine->_animations->initAnim(AnimationTypes::kClimbLadder, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx); // go up in ladder
}
}
}
}
break;
}
}
}
if (!flaggrm && actorIdx == _numObjFollow && _engine->_grid->_zoneGrm != -1) {
_engine->_grid->_zoneGrm = -1;
_engine->_grid->_indexGrm = -1;
_engine->_grid->copyMapToCube();
_engine->_redraw->_firstTime = true;
}
}
void Scene::stopRunningGame() {
free(_currentScene);
_currentScene = nullptr;
}
} // namespace TwinE

251
engines/twine/scene/scene.h Normal file
View File

@@ -0,0 +1,251 @@
/* 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 TWINE_SCENE_SCENE_H
#define TWINE_SCENE_SCENE_H
#include "common/scummsys.h"
#include "twine/scene/actor.h"
#include "twine/shared.h"
namespace TwinE {
#define NUM_SCENES_FLAGS 80
#define NUM_MAX_ACTORS 100
#define NUM_MAX_ZONES 100
#define NUM_MAX_TRACKS 200
enum class ScenePositionType {
kNoPosition = 0,
kZone = 1,
kScene = 2,
kReborn = 3
};
// ZONES
#define ZONE_INIT_ON 1
#define ZONE_ON 2
#define ZONE_ACTIVE 4
#define ZONE_OBLIGATOIRE 8
/**
* Special actions, like change scene, climbing a ladder, ...
*/
struct ZoneStruct {
IVec3 mins;
IVec3 maxs;
ZoneType type = ZoneType::kCube;
int32 num;
union {
struct {
int32 x;
int32 y;
int32 z;
} ChangeScene;
struct {
int32 x;
int32 y;
int32 z;
} CameraView;
/** show a text (e.g. when reading a sign) */
struct {
int32 textColor; /*!< text color (see @c ActorStruct::talkColor) */
} DisplayText;
struct {
BonusParameter typesFlag;
int32 amount;
/**
* Already used
*/
int32 used;
} Bonus;
struct {
int32 info0;
int32 info1;
int32 info2;
int32 info3;
int32 info4;
int32 info5;
int32 info6;
int32 info7;
} generic;
} infoData;
};
class TwinEEngine;
/**
* scene 0: 23 actors
*
* scene 1: 14 actors
* actor 1 - car
* actor 2 - elephant
* actor 3 - soldier at the house
* actor 4 - patrolling soldier before gate
* actor 5 - soldier after gate
* actor 6 - ??
* actor 7 - ??
* actor 8 - left gate
* actor 9 - ??
* actor 10 - door after leaving truck
* actor 11 - door subway
* actor 12 - guy at rubbish
* actor 13 - ??
*/
class Scene {
private:
TwinEEngine *_engine;
void loadModel(ActorStruct &actor, int32 modelIndex, bool lba1);
/** Process zone extra bonus */
void processZoneExtraBonus(ZoneStruct *zone);
void setActorStaticFlags(ActorStruct *act, uint32 staticFlags);
void setBonusParameterFlags(ActorStruct *act, uint16 bonusFlags);
bool loadSceneLBA1();
bool loadSceneLBA2();
/** Initialize new scene */
bool loadScene(int32 index);
/** Reset scene */
void clearScene();
// the first actor is the own hero
ActorStruct _sceneActors[NUM_MAX_ACTORS]; // ListObjet
int32 _currentSceneSize = 0;
bool _isOutsideScene = false; // lba2
/** Timer for the next sample ambience in scene */
int32 _timerNextAmbiance = 0;
int16 _sampleAmbiance[4]{0};
int16 _sampleRepeat[4]{0};
int16 _sampleRound[4]{0};
int16 _sampleFrequency[4]{0}; // lba2
int16 _sampleVolume[4]{0}; // lba2
int16 _sampleMinDelay = 0; // SecondMin
int16 _sampleMinDelayRnd = 0; // SecondEcart
int16 _samplePlayed = 0;
public:
int16 _cubeJingle = 0;
private:
IVec3 _sceneHeroPos; // CubeStartX, CubeStartY, CubeStartZ
IVec3 _zoneHeroPos; // NewPosX, NewPosY, NewPosZ
int32 _currentGameOverScene = 0;
uint8 *_currentScene = nullptr;
void dumpSceneScripts() const;
void dumpSceneScript(const char *type, int actorIdx, const uint8* script, int size) const;
public:
Scene(TwinEEngine *engine) : _engine(engine) {}
~Scene();
int32 _newCube = LBA1SceneId::Citadel_Island_Prison;
int32 _numCube = LBA1SceneId::Citadel_Island_Prison; // NumCube
int32 _oldcube = LBA1SceneId::Citadel_Island_Prison;
int32 _planet = -1;
int32 _numHolomapTraj = -1;
TextBankId _sceneTextBank = TextBankId::None;
int32 _alphaLight = 0;
int32 _betaLight = 0;
uint8 _island = 0;
uint8 _shadowLevel = 0; // lba2
uint8 _modeLabyrinthe = 0; // lba2
uint8 _cinemaMode = 0; // lba2
uint8 _currentCubeX = 0; // lba2
uint8 _currentCubeY = 0; // lba2
IVec3 _sceneStart;
/** Hero Y coordinate before fall */
int16 _startYFalling = 0;
/** Hero type of position in scene */
ScenePositionType _flagChgCube = ScenePositionType::kNoPosition; // twinsenPositionModeInNewCube
// ACTORS
int32 _nbObjets = 0;
ActorStruct *_sceneHero = nullptr;
/** Meca penguin actor index */
int16 _mecaPenguinIdx = 0;
/** Current followed actor in scene */
int16 _numObjFollow = OWN_ACTOR_SCENE_INDEX;
/** Current actor in zone - climbing a ladder */
bool _flagClimbing = false;
/** Current actor manipulated in scripts */
int16 _currentScriptValue = 0;
int16 _talkingActor = 0;
// TRACKS Tell the actor where to go
int32 _sceneNumTracks = 0;
IVec3 _sceneTracks[NUM_MAX_TRACKS];
bool _flagRenderGrid = true; // FlagAffGrille
uint8 _listFlagCube[NUM_SCENES_FLAGS]{0}; // ListVarCube
int32 _sceneNumZones = 0; // NbZones
ZoneStruct _sceneZones[NUM_MAX_ZONES]; // ListZone
ActorStruct *getActor(int32 actorIdx); // ListObjet
void playSceneMusic();
void reloadCurrentScene();
/** Change to another scene */
void changeCube();
/** For the buggy to get the 2D coordinates of an exterior cube in the map */
bool loadSceneCubeXY(int sceneNum, int32 *cubeX, int32 *cubeY);
/** Process scene environment sound */
void processEnvironmentSound(); // GereAmbiance
void initSceneVars();
bool isGameRunning() const;
void stopRunningGame();
/**
* Process actor zones
* @param actorIdx Process actor index
*/
void checkZoneSce(int32 actorIdx);
};
inline bool Scene::isGameRunning() const {
return _currentScene != nullptr;
}
} // namespace TwinE
#endif

View File

@@ -0,0 +1,42 @@
/* 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 "twine/scene/wagon.h"
namespace TwinE {
void Wagon::DoAnimWagon() {
}
void Wagon::DoDirWagon(ActorStruct *ptrobj) {
}
int32 Wagon::GetNumBrickWagon(int32 brick) {
return -1;
}
void Wagon::AdjustEssieuWagonAvant(int32 brickw) {
}
void Wagon::AdjustEssieuWagonArriere(int32 brickw) {
}
} // namespace TwinE

View File

@@ -0,0 +1,46 @@
/* 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 TWINE_SCENE_WAGON_H
#define TWINE_SCENE_WAGON_H
#include "twine/scene/actor.h"
#include "twine/twine.h"
namespace TwinE {
class Wagon {
private:
//TwinEEngine *_engine;
public:
Wagon(TwinEEngine *engine) /* : _engine(engine) */ {}
void DoAnimWagon();
void DoDirWagon(ActorStruct *ptrobj);
int32 GetNumBrickWagon(int32 brick);
void AdjustEssieuWagonAvant(int32 brickw);
void AdjustEssieuWagonArriere(int32 brickw);
};
} // namespace TwinE
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
/* 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 TWINE_SCRIPTLIFE_H
#define TWINE_SCRIPTLIFE_H
#include "common/scummsys.h"
#include "twine/scene/actor.h"
namespace TwinE {
struct LifeScriptContext {
int32 actorIdx;
ActorStruct *actor;
Common::MemorySeekableReadWriteStream stream;
uint8 *opcodePtr; // local opcode script pointer
LifeScriptContext(int32 _actorIdx, ActorStruct *_actor) : actorIdx(_actorIdx), actor(_actor), stream(_actor->_lifeScript, _actor->_lifeScriptSize) {
assert(actor->_offsetLife >= 0);
stream.skip(_actor->_offsetLife);
updateOpcodePos();
}
void setOpcode(uint8 opcode) {
*opcodePtr = opcode;
}
void updateOpcodePos() {
opcodePtr = actor->_lifeScript + stream.pos();
}
};
/**
* Returns @c -1 Need implementation, @c 0 Condition false, @c 1 - Condition true
*/
typedef int32 ScriptLifeFunc(TwinEEngine *engine, LifeScriptContext &ctx);
struct ScriptLifeFunction {
const char *name;
ScriptLifeFunc *function;
};
/** Script condition operators */
enum LifeScriptOperators {
/*==*/kEqualTo = 0,
/*> */kGreaterThan = 1,
/*< */kLessThan = 2,
/*>=*/kGreaterThanOrEqualTo = 3,
/*<=*/kLessThanOrEqualTo = 4,
/*!=*/kNotEqualTo = 5
};
class ScriptLife {
private:
TwinEEngine *_engine;
const ScriptLifeFunction* _functionMap;
size_t _functionMapSize;
public:
static int32 lEMPTY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lEND(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lNOP(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSNIF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lOFFSET(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lNEVERIF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lNO_IF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lLABEL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lRETURN(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lIF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSWIF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lONEIF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lELSE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBODY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBODY_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lANIM(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lANIM_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_LIFE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_LIFE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_TRACK(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_TRACK_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lMESSAGE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFALLABLE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_DIRMODE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_DIRMODE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCAM_FOLLOW(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_BEHAVIOUR(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_FLAG_CUBE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCOMPORTEMENT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_COMPORTEMENT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_COMPORTEMENT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lEND_COMPORTEMENT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lKILL_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSUICIDE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lUSE_ONE_LITTLE_KEY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lGIVE_GOLD_PIECES(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lEND_LIFE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSTOP_L_TRACK(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lRESTORE_L_TRACK(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lMESSAGE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lINC_CHAPTER(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFOUND_OBJECT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_DOOR_LEFT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_DOOR_RIGHT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_DOOR_UP(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_DOOR_DOWN(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lGIVE_BONUS(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCHANGE_CUBE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lOBJ_COL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBRICK_COL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lOR_IF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lINVISIBLE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lZOOM(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPOS_POINT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_MAGIC_LEVEL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSUB_MAGIC_POINT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_LIFE_POINT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSUB_LIFE_POINT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lHIT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPLAY_FLA(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lINC_CLOVER_BOX(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_USED_INVENTORY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lADD_CHOICE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lASK_CHOICE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBIG_MESSAGE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lINIT_PINGOUIN(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_HOLO_POS(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCLR_HOLO_POS(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lADD_FUEL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSUB_FUEL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_GRM(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAY_MESSAGE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAY_MESSAGE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFULL_POINT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBETA(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lGRM_OFF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFADE_PAL_RED(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFADE_ALARM_RED(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFADE_ALARM_PAL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFADE_RED_PAL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFADE_RED_ALARM(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFADE_PAL_ALARM(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lEXPLODE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lASK_CHOICE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_DARK_PAL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_NORMAL_PAL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lMESSAGE_SENDELL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lANIM_SET(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lHOLOMAP_TRAJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lGAME_OVER(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lTHE_END(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPLAY_CD_TRACK(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPROJ_ISO(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPROJ_3D(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lTEXT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCLEAR_TEXT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBRUTAL_EXIT(TwinEEngine *engine, LifeScriptContext &ctx);
public:
ScriptLife(TwinEEngine *engine, const ScriptLifeFunction* functionMap, size_t entries);
virtual ~ScriptLife() {}
/**
* Process actor life script
* @param actorIdx Current processed actor index
*/
void doLife(int32 actorIdx);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,196 @@
/* 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 "twine/script/script_life_v1.h"
#include "common/debug.h"
#include "twine/twine.h"
#include "twine/text.h"
#include "twine/audio/music.h"
#include "twine/scene/gamestate.h"
namespace TwinE {
/**
* Turn on bubbles while actors talk.
* @note Opcode @c 0x59
*/
int32 ScriptLifeV1::lBUBBLE_ON(TwinEEngine *engine, LifeScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::BUBBLE_ON()");
engine->_text->_showDialogueBubble = true;
return 0;
}
/**
* Turn off bubbles while actors talk.
* @note Opcode @c 0x5A
*/
int32 ScriptLifeV1::lBUBBLE_OFF(TwinEEngine *engine, LifeScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::BUBBLE_OFF()");
engine->_text->_showDialogueBubble = false;
return 0;
}
/**
* Play Midis (Parameter = Midis Index)
* @note Opcode @c 0x41
*/
int32 ScriptLifeV1::lPLAY_MIDI(TwinEEngine *engine, LifeScriptContext &ctx) {
const int32 midiIdx = ctx.stream.readByte();
engine->_music->playMusic(midiIdx);
debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::PLAY_MIDI(%i)", (int)midiIdx);
return 0;
}
/**
* Stop the current played midi.
* @note Opcode @c 0x63
*/
int32 ScriptLifeV1::lMIDI_OFF(TwinEEngine *engine, LifeScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::MIDI_OFF()");
engine->_music->stopMusicMidi();
return 0;
}
/**
* Set a new value for the game flag (Paramter = Game Flag Index, Parameter = Value)
* @note Opcode @c 0x24
*/
int32 ScriptLifeV1::lSET_FLAG_GAME(TwinEEngine *engine, LifeScriptContext &ctx) {
const uint8 flagIdx = ctx.stream.readByte();
const uint8 flagValue = ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::SET_FLAG_GAME(%i, %i)", (int)flagIdx, (int)flagValue);
engine->_gameState->setGameFlag(flagIdx, flagValue);
return 0;
}
static const ScriptLifeFunction function_map[] = {
{"END", ScriptLifeV1::lEND},
{"NOP", ScriptLifeV1::lNOP},
{"SNIF", ScriptLifeV1::lSNIF},
{"OFFSET", ScriptLifeV1::lOFFSET},
{"NEVERIF", ScriptLifeV1::lNEVERIF},
{"", ScriptLifeV1::lEMPTY}, // unused
{"NO_IF", ScriptLifeV1::lNO_IF},
{"", ScriptLifeV1::lEMPTY}, // unused
{"", ScriptLifeV1::lEMPTY}, // unused
{"", ScriptLifeV1::lEMPTY}, // unused
{"LABEL", ScriptLifeV1::lLABEL},
{"RETURN", ScriptLifeV1::lRETURN},
{"IF", ScriptLifeV1::lIF},
{"SWIF", ScriptLifeV1::lSWIF},
{"ONEIF", ScriptLifeV1::lONEIF},
{"ELSE", ScriptLifeV1::lELSE},
{"ENDIF", ScriptLifeV1::lEMPTY}, // End of a conditional statement (e.g. IF)
{"BODY", ScriptLifeV1::lBODY},
{"BODY_OBJ", ScriptLifeV1::lBODY_OBJ},
{"ANIM", ScriptLifeV1::lANIM},
{"ANIM_OBJ", ScriptLifeV1::lANIM_OBJ},
{"SET_LIFE", ScriptLifeV1::lSET_LIFE},
{"SET_LIFE_OBJ", ScriptLifeV1::lSET_LIFE_OBJ},
{"SET_TRACK", ScriptLifeV1::lSET_TRACK},
{"SET_TRACK_OBJ", ScriptLifeV1::lSET_TRACK_OBJ},
{"MESSAGE", ScriptLifeV1::lMESSAGE},
{"FALLABLE", ScriptLifeV1::lFALLABLE},
{"SET_DIRMODE", ScriptLifeV1::lSET_DIRMODE},
{"SET_DIRMODE_OBJ", ScriptLifeV1::lSET_DIRMODE_OBJ},
{"CAM_FOLLOW", ScriptLifeV1::lCAM_FOLLOW},
{"SET_BEHAVIOUR", ScriptLifeV1::lSET_BEHAVIOUR},
{"SET_FLAG_CUBE", ScriptLifeV1::lSET_FLAG_CUBE},
{"COMPORTEMENT", ScriptLifeV1::lCOMPORTEMENT},
{"SET_COMPORTEMENT", ScriptLifeV1::lSET_COMPORTEMENT},
{"SET_COMPORTEMENT_OBJ", ScriptLifeV1::lSET_COMPORTEMENT_OBJ},
{"END_COMPORTEMENT", ScriptLifeV1::lEND_COMPORTEMENT},
{"SET_FLAG_GAME", ScriptLifeV1::lSET_FLAG_GAME},
{"KILL_OBJ", ScriptLifeV1::lKILL_OBJ},
{"SUICIDE", ScriptLifeV1::lSUICIDE},
{"USE_ONE_LITTLE_KEY", ScriptLifeV1::lUSE_ONE_LITTLE_KEY},
{"GIVE_GOLD_PIECES", ScriptLifeV1::lGIVE_GOLD_PIECES},
{"END_LIFE", ScriptLifeV1::lEND_LIFE},
{"STOP_L_TRACK", ScriptLifeV1::lSTOP_L_TRACK},
{"RESTORE_L_TRACK", ScriptLifeV1::lRESTORE_L_TRACK},
{"MESSAGE_OBJ", ScriptLifeV1::lMESSAGE_OBJ},
{"INC_CHAPTER", ScriptLifeV1::lINC_CHAPTER},
{"FOUND_OBJECT", ScriptLifeV1::lFOUND_OBJECT},
{"SET_DOOR_LEFT", ScriptLifeV1::lSET_DOOR_LEFT},
{"SET_DOOR_RIGHT", ScriptLifeV1::lSET_DOOR_RIGHT},
{"SET_DOOR_UP", ScriptLifeV1::lSET_DOOR_UP},
{"SET_DOOR_DOWN", ScriptLifeV1::lSET_DOOR_DOWN},
{"GIVE_BONUS", ScriptLifeV1::lGIVE_BONUS},
{"CHANGE_CUBE", ScriptLifeV1::lCHANGE_CUBE},
{"OBJ_COL", ScriptLifeV1::lOBJ_COL},
{"BRICK_COL", ScriptLifeV1::lBRICK_COL},
{"OR_IF", ScriptLifeV1::lOR_IF},
{"INVISIBLE", ScriptLifeV1::lINVISIBLE},
{"ZOOM", ScriptLifeV1::lZOOM},
{"POS_POINT", ScriptLifeV1::lPOS_POINT},
{"SET_MAGIC_LEVEL", ScriptLifeV1::lSET_MAGIC_LEVEL},
{"SUB_MAGIC_POINT", ScriptLifeV1::lSUB_MAGIC_POINT},
{"SET_LIFE_POINT_OBJ", ScriptLifeV1::lSET_LIFE_POINT_OBJ},
{"SUB_LIFE_POINT_OBJ", ScriptLifeV1::lSUB_LIFE_POINT_OBJ},
{"HIT_OBJ", ScriptLifeV1::lHIT_OBJ},
{"PLAY_FLA", ScriptLifeV1::lPLAY_FLA},
{"PLAY_MIDI", ScriptLifeV1::lPLAY_MIDI},
{"INC_CLOVER_BOX", ScriptLifeV1::lINC_CLOVER_BOX},
{"SET_USED_INVENTORY", ScriptLifeV1::lSET_USED_INVENTORY},
{"ADD_CHOICE", ScriptLifeV1::lADD_CHOICE},
{"ASK_CHOICE", ScriptLifeV1::lASK_CHOICE},
{"BIG_MESSAGE", ScriptLifeV1::lBIG_MESSAGE},
{"INIT_PINGOUIN", ScriptLifeV1::lINIT_PINGOUIN},
{"SET_HOLO_POS", ScriptLifeV1::lSET_HOLO_POS},
{"CLR_HOLO_POS", ScriptLifeV1::lCLR_HOLO_POS},
{"ADD_FUEL", ScriptLifeV1::lADD_FUEL},
{"SUB_FUEL", ScriptLifeV1::lSUB_FUEL},
{"SET_GRM", ScriptLifeV1::lSET_GRM},
{"SAY_MESSAGE", ScriptLifeV1::lSAY_MESSAGE},
{"SAY_MESSAGE_OBJ", ScriptLifeV1::lSAY_MESSAGE_OBJ},
{"FULL_POINT", ScriptLifeV1::lFULL_POINT},
{"BETA", ScriptLifeV1::lBETA},
{"GRM_OFF", ScriptLifeV1::lGRM_OFF},
{"FADE_PAL_RED", ScriptLifeV1::lFADE_PAL_RED},
{"FADE_ALARM_RED", ScriptLifeV1::lFADE_ALARM_RED},
{"FADE_ALARM_PAL", ScriptLifeV1::lFADE_ALARM_PAL},
{"FADE_RED_PAL", ScriptLifeV1::lFADE_RED_PAL},
{"FADE_RED_ALARM", ScriptLifeV1::lFADE_RED_ALARM},
{"FADE_PAL_ALARM", ScriptLifeV1::lFADE_PAL_ALARM},
{"EXPLODE_OBJ", ScriptLifeV1::lEXPLODE_OBJ},
{"BUBBLE_ON", ScriptLifeV1::lBUBBLE_ON},
{"BUBBLE_OFF", ScriptLifeV1::lBUBBLE_OFF},
{"ASK_CHOICE_OBJ", ScriptLifeV1::lASK_CHOICE_OBJ},
{"SET_DARK_PAL", ScriptLifeV1::lSET_DARK_PAL},
{"SET_NORMAL_PAL", ScriptLifeV1::lSET_NORMAL_PAL},
{"MESSAGE_SENDELL", ScriptLifeV1::lMESSAGE_SENDELL},
{"ANIM_SET", ScriptLifeV1::lANIM_SET},
{"HOLOMAP_TRAJ", ScriptLifeV1::lHOLOMAP_TRAJ},
{"GAME_OVER", ScriptLifeV1::lGAME_OVER},
{"THE_END", ScriptLifeV1::lTHE_END},
{"MIDI_OFF", ScriptLifeV1::lMIDI_OFF},
{"PLAY_CD_TRACK", ScriptLifeV1::lPLAY_CD_TRACK},
{"PROJ_ISO", ScriptLifeV1::lPROJ_ISO},
{"PROJ_3D", ScriptLifeV1::lPROJ_3D},
{"TEXT", ScriptLifeV1::lTEXT},
{"CLEAR_TEXT", ScriptLifeV1::lCLEAR_TEXT},
{"BRUTAL_EXIT", ScriptLifeV1::lBRUTAL_EXIT}
};
ScriptLifeV1::ScriptLifeV1(TwinEEngine *engine) : ScriptLife(engine, function_map, ARRAYSIZE(function_map)) {
}
} // namespace TwinE

View File

@@ -0,0 +1,44 @@
/* 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 TWINE_SCRIPTLIFEV1_H
#define TWINE_SCRIPTLIFEV1_H
#include "twine/script/script_life.h"
namespace TwinE {
class TwinEEngine;
class ScriptLifeV1 : public ScriptLife {
public:
static int32 lBUBBLE_ON(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBUBBLE_OFF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPLAY_MIDI(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lMIDI_OFF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_FLAG_GAME(TwinEEngine *engine, LifeScriptContext &ctx);
ScriptLifeV1(TwinEEngine *engine);
};
} // namespace TwinE
#endif

File diff suppressed because it is too large Load Diff

View 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/>.
*
*/
#ifndef TWINE_SCRIPTLIFEV2_H
#define TWINE_SCRIPTLIFEV2_H
#include "twine/script/script_life.h"
namespace TwinE {
class TwinEEngine;
class ScriptLifeV2 : public ScriptLife {
private:
static int16 searchOffsetTrack(ActorStruct *ptrobj, uint8 label);
static void cleanTrack(ActorStruct *ptrobj);
public:
static int32 lPLAY_MUSIC(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lTRACK_TO_VAR_GAME(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lVAR_GAME_TO_TRACK(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lANIM_TEXTURE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lADD_MESSAGE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lADD_MESSAGE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBUBBLE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lNO_CHOC(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAVE_HERO(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lRESTORE_HERO(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCINEMA_MODE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lESCALATOR(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lRAIN(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCAMERA_CENTER(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_CAMERA(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSHADOW_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPLAY_ACF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lECLAIR(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lINIT_BUGGY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lMEMO_ARDOISE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_CHANGE_CUBE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lMESSAGE_ZOE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFADE_TO_PAL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lACTION(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_FRAME(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_SPRITE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_FRAME_3DS(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lIMPACT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lIMPACT_POINT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPALETTE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lLADDER(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_ARMOR(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_ARMOR_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lADD_LIFE_POINT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSTATE_INVENTORY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lAND_IF(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSWITCH(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lOR_CASE (TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lCASE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lDEFAULT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBREAK(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lEND_SWITCH(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_HIT_ZONE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAVE_COMPORTEMENT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lRESTORE_COMPORTEMENT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAMPLE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAMPLE_RND(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAMPLE_ALWAYS(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAMPLE_STOP (TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lREPEAT_SAMPLE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lBACKGROUND(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lADD_VAR_GAME(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSUB_VAR_GAME(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lADD_VAR_CUBE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSUB_VAR_CUBE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_RAIL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lINVERSE_BETA(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lNO_BODY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSTOP_L_TRACK_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lRESTORE_L_TRACK_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSAVE_COMPORTEMENT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lRESTORE_COMPORTEMENT_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSPY(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lDEBUG(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lDEBUG_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPOPCORN(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFLOW_POINT(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lFLOW_OBJ (TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_ANIM_DIAL(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPCX(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lEND_MESSAGE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lEND_MESSAGE_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPARM_SAMPLE(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lNEW_SAMPLE (TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPOS_OBJ_AROUND(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lPCX_MESS_OBJ(TwinEEngine *engine, LifeScriptContext &ctx);
static int32 lSET_FLAG_GAME(TwinEEngine *engine, LifeScriptContext &ctx);
ScriptLifeV2(TwinEEngine *engine);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,671 @@
/* 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 "twine/script/script_move.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "twine/scene/animations.h"
#include "twine/audio/sound.h"
#include "twine/movies.h"
#include "twine/scene/movements.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/scene/scene.h"
#include "twine/twine.h"
namespace TwinE {
/**
* For unused opcodes
*/
int32 ScriptMove::mEMPTY(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::EMPTY()");
return 0;
}
/**
* End of Actor Move Script
* @note Opcode @c 0x00
*/
int32 ScriptMove::mEND(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::END()");
ctx.actor->_offsetTrack = -1;
return 1;
}
/**
* No Operation
* @note Opcode @c 0x01
*/
int32 ScriptMove::mNOP(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::NOP()");
return 0;
}
/**
* Choose new body for the current actor (Parameter = File3D Body Instance)
* @note Opcode @c 0x02
*/
int32 ScriptMove::mBODY(TwinEEngine *engine, MoveScriptContext &ctx) {
BodyType bodyIdx = (BodyType)ctx.stream.readByte();
engine->_actor->initBody(bodyIdx, ctx.actorIdx);
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::BODY(%i)", (int)bodyIdx);
return 0;
}
/**
* Choose new animation for the current actor (Parameter = File3D Animation Instance)
* @note Opcode @c 0x03
*/
int32 ScriptMove::mANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
AnimationTypes animIdx = (AnimationTypes)ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::ANIM(%i)", (int)animIdx);
if (engine->_animations->initAnim(animIdx, AnimType::kAnimationTypeRepeat, AnimationTypes::kStanding, ctx.actorIdx)) {
return 0;
}
ctx.undo(1);
return 1;
}
/**
* Tell the actor to go to a new position (Parameter = Track Index)
* @note Opcode @c 0x04
*/
int32 ScriptMove::mGOTO_POINT(TwinEEngine *engine, MoveScriptContext &ctx) {
engine->_scene->_currentScriptValue = ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::GOTO_POINT(%i)", (int)engine->_scene->_currentScriptValue);
const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
const int32 newAngle = engine->_movements->getAngle(ctx.actor->_posObj.x, ctx.actor->_posObj.z, sp.x, sp.z);
if (ctx.actor->_flags.bSprite3D) {
ctx.actor->_beta = newAngle;
} else {
engine->_movements->initRealAngleConst(ctx.actor->_beta, newAngle, ctx.actor->_srot, &ctx.actor->realAngle);
}
if (engine->_movements->_targetActorDistance > 500) {
ctx.undo(1);
return 1;
}
return 0;
}
/**
* Wait the end of the current animation
* @note Opcode @c 0x05
*/
int32 ScriptMove::mWAIT_ANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::WAIT_ANIM()");
if (!ctx.actor->_workFlags.bAnimEnded) {
ctx.undo(0);
} else {
engine->_movements->clearRealAngle(ctx.actor);
}
return 1;
}
/**
* Loop a certain label (Parameter = Label Number)
* @note Opcode @c 0x06
*/
int32 ScriptMove::mLOOP(TwinEEngine *engine, MoveScriptContext &ctx) {
ctx.actor->_offsetTrack = 0;
ctx.stream.seek(0);
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::LOOP()");
return 0;
}
/**
* Make the actor turn around
* @note Opcode @c 0x07
*/
int32 ScriptMove::mANGLE(TwinEEngine *engine, MoveScriptContext &ctx) {
const int16 angle = ToAngle(ctx.stream.readSint16LE());
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::ANGLE(%i)", (int)angle);
if (ctx.actor->_flags.bSprite3D) {
return 0;
}
engine->_scene->_currentScriptValue = angle;
if (ctx.actor->realAngle.timeValue == 0) {
engine->_movements->initRealAngleConst(ctx.actor->_beta, angle, ctx.actor->_srot, &ctx.actor->realAngle);
}
if (ctx.actor->_beta == angle) {
engine->_movements->clearRealAngle(ctx.actor);
return 0;
}
ctx.undo(2);
return 1;
}
/**
* Set new position for the current actor (Parameter = Track Index)
* @note Opcode @c 0x08
*/
int32 ScriptMove::mPOS_POINT(TwinEEngine *engine, MoveScriptContext &ctx) {
engine->_scene->_currentScriptValue = ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::POS_POINT(%i)", (int)engine->_scene->_currentScriptValue);
const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
if (ctx.actor->_flags.bSprite3D) {
ctx.actor->_srot = 0;
}
ctx.actor->_posObj = sp;
return 0;
}
/**
* Specify a new label (Parameter = Label Number)
* @note Opcode @c 0x09
*/
int32 ScriptMove::mLABEL(TwinEEngine *engine, MoveScriptContext &ctx) {
ctx.actor->_labelTrack = ctx.stream.readByte();
ctx.actor->_offsetLabelTrack = ctx.stream.pos() - 2;
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::LABEL(%i)", (int)ctx.actor->_labelTrack);
if (engine->_scene->_numCube == LBA1SceneId::Proxima_Island_Museum && ctx.actor->_actorIdx == 2 &&
(ctx.actor->_labelTrack == 0 || ctx.actor->_labelTrack == 1)) {
engine->unlockAchievement("LBA_ACH_004");
}
return 0;
}
/**
* Go to a certain label (Parameter = Label Number)
* @note Opcode @c 0x0A
*/
int32 ScriptMove::mGOTO(TwinEEngine *engine, MoveScriptContext &ctx) {
const int16 pos = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::GOTO(%i)", (int)pos);
if (pos == -1) {
ctx.actor->_offsetTrack = -1;
return 1;
}
ctx.stream.seek(pos);
return 0;
}
/**
* Tell the actor to stop the current animation
* @note Opcode @c 0x0B
*/
int32 ScriptMove::mSTOP(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::STOP()");
ctx.actor->_offsetTrack = -1;
return 1;
}
/**
* Tell the actor to go to a symbolic point
* @note Opcode @c 0x0C
*/
int32 ScriptMove::mGOTO_SYM_POINT(TwinEEngine *engine, MoveScriptContext &ctx) {
engine->_scene->_currentScriptValue = ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::GOTO_SYM_POINT(%i)", (int)engine->_scene->_currentScriptValue);
const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
const int32 newAngle = LBAAngles::ANGLE_180 + engine->_movements->getAngle(ctx.actor->_posObj, sp);
if (ctx.actor->_flags.bSprite3D) {
ctx.actor->_beta = newAngle;
} else {
engine->_movements->initRealAngleConst(ctx.actor->_beta, newAngle, ctx.actor->_srot, &ctx.actor->realAngle);
}
if (engine->_movements->_targetActorDistance > 500) {
ctx.undo(1);
return 1;
}
return 0;
}
/**
* Wait a certain number of frame update in the current animation
* @note Opcode @c 0x0D
*/
int32 ScriptMove::mWAIT_NUM_ANIM(TwinEEngine *engine, MoveScriptContext &ctx) {
bool abortMove = false;
const int32 animRepeats = ctx.stream.readByte();
int32 animPos = ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::WAIT_NUM_ANIM(%i, %i)", (int)animRepeats, animPos);
if (ctx.actor->_workFlags.bAnimEnded) {
animPos++;
if (animPos == animRepeats) {
animPos = 0;
} else {
abortMove = true;
}
ctx.stream.rewind(1);
ctx.stream.writeByte(animPos);
} else {
abortMove = true;
}
if (abortMove) {
ctx.undo(2);
}
return abortMove ? 1 : 0;
}
/**
* Play a sample (Parameter = Sample index)
* @note Opcode @c 0x0E
*/
int32 ScriptMove::mSAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
int32 sampleIdx = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE(%i)", (int)sampleIdx);
engine->_sound->mixSample3D(sampleIdx, 0x1000, 1, ctx.actor->posObj(), ctx.actorIdx);
return 0;
}
/**
* Tell the actor to go to a new position (Parameter = Track Index)
* @note Opcode @c 0x0F
*/
int32 ScriptMove::mGOTO_POINT_3D(TwinEEngine *engine, MoveScriptContext &ctx) {
const int32 trackId = ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::GOTO_POINT_3D(%i)", (int)trackId);
if (!ctx.actor->_flags.bSprite3D) {
return 0;
}
engine->_scene->_currentScriptValue = trackId;
const IVec3 &sp = engine->_scene->_sceneTracks[engine->_scene->_currentScriptValue];
ctx.actor->_beta = engine->_movements->getAngle(ctx.actor->_posObj.x, ctx.actor->_posObj.z, sp.x, sp.z);
ctx.actor->_spriteActorRotation = engine->_movements->getAngle(ctx.actor->_posObj.y, 0, sp.y, engine->_movements->_targetActorDistance);
if (engine->_movements->_targetActorDistance > 100) {
ctx.undo(1);
return 1;
}
ctx.actor->_posObj = sp;
return 0;
}
/**
* Specify a new rotation speed for the current actor (Parameter = Rotation speed) [ 0 means fast, 32767 means slow ]
* @note Opcode @c 0x10
*/
int32 ScriptMove::mSPEED(TwinEEngine *engine, MoveScriptContext &ctx) {
ctx.actor->_srot = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SPEED(%i)", (int)ctx.actor->_srot);
if (ctx.actor->_flags.bSprite3D) {
engine->_movements->initRealValue(LBAAngles::ANGLE_0, ctx.actor->_srot, LBAAngles::ANGLE_17, &ctx.actor->realAngle);
}
return 0;
}
/**
* Set actor as background (Parameter = 1 (true); = 0 (false))
* @note Opcode @c 0x11
*/
int32 ScriptMove::mBACKGROUND(TwinEEngine *engine, MoveScriptContext &ctx) {
const uint8 val = ctx.stream.readByte();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::BACKGROUND(%i)", (int)val);
if (val != 0) {
if (!ctx.actor->_flags.bIsBackgrounded) {
ctx.actor->_flags.bIsBackgrounded = 1;
if (ctx.actor->_workFlags.bWasDrawn) {
engine->_redraw->_firstTime = true;
}
}
} else {
if (ctx.actor->_flags.bIsBackgrounded) {
ctx.actor->_flags.bIsBackgrounded = 0;
if (ctx.actor->_workFlags.bWasDrawn) {
engine->_redraw->_firstTime = true;
}
}
}
return 0;
}
/**
* Number os seconds to wait.
* @note Opcode @c 0x12
*/
int32 ScriptMove::mWAIT_NUM_SECOND(TwinEEngine *engine, MoveScriptContext &ctx) {
const int32 numSeconds = ctx.stream.readByte();
int32 currentTime = ctx.stream.readSint32LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::WAIT_NUM_SECOND(%i, %i)", (int)numSeconds, currentTime);
if (currentTime == 0) {
currentTime = engine->timerRef + engine->toSeconds(numSeconds);
ctx.stream.rewind(4);
ctx.stream.writeSint32LE(currentTime);
}
if (engine->timerRef < currentTime) {
ctx.undo(5);
return 1;
}
ctx.stream.rewind(4);
ctx.stream.writeSint32LE(0);
return 0;
}
/**
* To not use Bodies.
* @note Opcode @c 0x13
*/
int32 ScriptMove::mNO_BODY(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::NO_BODY()");
engine->_actor->initBody(BodyType::btNone, ctx.actorIdx);
return 0;
}
/**
* Change actor orientation. (Parameter = New Angle)
* @note Opcode @c 0x14
*/
int32 ScriptMove::mBETA(TwinEEngine *engine, MoveScriptContext &ctx) {
const int16 beta = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::BETA(%i)", (int)beta);
ctx.actor->_beta = beta;
if (!ctx.actor->_flags.bSprite3D) {
engine->_movements->clearRealAngle(ctx.actor);
}
return 0;
}
int32 ScriptMove::mOPEN_GENERIC(TwinEEngine *engine, MoveScriptContext &ctx, int32 angle) {
const int16 doorStatus = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::OPEN(%i, %i)", (int)doorStatus, angle);
if (ctx.actor->_flags.bSprite3D && ctx.actor->_flags.bSpriteClip) {
ctx.actor->_beta = angle;
ctx.actor->_doorWidth = doorStatus;
ctx.actor->_workFlags.bIsSpriteMoving = 1;
ctx.actor->_srot = 1000;
engine->_movements->initRealValue(LBAAngles::ANGLE_0, LBAAngles::ANGLE_351, LBAAngles::ANGLE_17, &ctx.actor->realAngle);
}
if (engine->_scene->_numCube == LBA1SceneId::Proxima_Island_Museum && ctx.actor->_actorIdx == 16) {
engine->unlockAchievement("LBA_ACH_009");
}
return 0;
}
/**
* Open the door (left way) (Parameter = distance to open).
* @note Opcode @c 0x15
*/
int32 ScriptMove::mOPEN_LEFT(TwinEEngine *engine, MoveScriptContext &ctx) {
return mOPEN_GENERIC(engine, ctx, LBAAngles::ANGLE_270);
}
/**
* Open the door (right way) (Parameter = distance to open).
* @note Opcode @c 0x16
*/
int32 ScriptMove::mOPEN_RIGHT(TwinEEngine *engine, MoveScriptContext &ctx) {
return mOPEN_GENERIC(engine, ctx, LBAAngles::ANGLE_90);
}
/**
* Open the door (up way) (Parameter = distance to open).
* @note Opcode @c 0x17
*/
int32 ScriptMove::mOPEN_UP(TwinEEngine *engine, MoveScriptContext &ctx) {
return mOPEN_GENERIC(engine, ctx, LBAAngles::ANGLE_180);
}
/**
* Open the door (down way) (Parameter = distance to open).
* @note Opcode @c 0x18
*/
int32 ScriptMove::mOPEN_DOWN(TwinEEngine *engine, MoveScriptContext &ctx) {
return mOPEN_GENERIC(engine, ctx, LBAAngles::ANGLE_0);
}
/**
* Close the door.
* @note Opcode @c 0x19
*/
int32 ScriptMove::mCLOSE(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::CLOSE()");
if (ctx.actor->_flags.bSprite3D && ctx.actor->_flags.bSpriteClip) {
ctx.actor->_doorWidth = 0;
ctx.actor->_workFlags.bIsSpriteMoving = 1;
ctx.actor->_srot = -1000;
engine->_movements->initRealValue(LBAAngles::ANGLE_0, -LBAAngles::ANGLE_351, LBAAngles::ANGLE_17, &ctx.actor->realAngle);
}
return 0;
}
/**
* Wait till door close.
* @note Opcode @c 0x1A
*/
int32 ScriptMove::mWAIT_DOOR(TwinEEngine *engine, MoveScriptContext &ctx) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::WAIT_DOOR()");
if (ctx.actor->_flags.bSprite3D && ctx.actor->_flags.bSpriteClip) {
if (ctx.actor->_srot) {
ctx.undo(0);
return 1;
}
}
return 0;
}
/**
* Generate a random sample.
* @note Opcode @c 0x1B
*/
int32 ScriptMove::mSAMPLE_RND(TwinEEngine *engine, MoveScriptContext &ctx) {
int32 sampleIdx = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_RND(%i)", (int)sampleIdx);
const uint16 pitchbend = 0x800 + engine->getRandomNumber(0x800);
engine->_sound->mixSample3D(sampleIdx, pitchbend, 1, ctx.actor->posObj(), ctx.actorIdx);
return 0;
}
/**
* Play always the sample (Parameter = Sample index)
* @note Opcode @c 0x1C
*/
int32 ScriptMove::mSAMPLE_ALWAYS(TwinEEngine *engine, MoveScriptContext &ctx) {
int32 sampleIdx = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_ALWAYS(%i)", (int)sampleIdx);
if (!engine->_sound->isSamplePlaying(sampleIdx)) { // if its not playing
engine->_sound->mixSample3D(sampleIdx, 0x1000, 0, ctx.actor->posObj(), ctx.actorIdx);
}
return 0;
}
/**
* Stop playing the sample
* @note Opcode @c 0x1D
*/
int32 ScriptMove::mSAMPLE_STOP(TwinEEngine *engine, MoveScriptContext &ctx) {
int32 sampleIdx = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SAMPLE_STOP(%i)", (int)sampleIdx);
engine->_sound->stopSample(sampleIdx);
return 0;
}
/**
* Play FLA cutscenes (Parameter = Cutscene Name)
* @note Opcode @c 0x1E
*/
int32 ScriptMove::mPLAY_FLA(TwinEEngine *engine, MoveScriptContext &ctx) {
int strIdx = 0;
char movie[64];
do {
const byte c = ctx.stream.readByte();
movie[strIdx++] = c;
if (c == '\0') {
break;
}
if (strIdx >= ARRAYSIZE(movie)) {
error("Max string size exceeded for fla name");
}
} while (true);
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::PLAY_FLA(%s)", movie);
engine->saveTimer(false);
engine->_screens->fadeToBlack(engine->_screens->_ptrPal);
engine->_movie->playMovie(movie);
engine->_screens->_flagFade = true;
engine->restoreTimer();
engine->_redraw->drawScene(true);
return 0;
}
/**
* Repeat sample (Parameter = Sample index).
* @note Opcode @c 0x1F
*/
int32 ScriptMove::mREPEAT_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
ctx.bigSampleRepeat = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::REPEAT_SAMPLE(%i)", (int)ctx.bigSampleRepeat);
return 0;
}
/**
* Play a sample (Parameter = Sample index)
* @note Opcode @c 0x20
*/
int32 ScriptMove::mSIMPLE_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx) {
int32 sampleIdx = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::SIMPLE_SAMPLE(%i)", (int)sampleIdx);
engine->_sound->mixSample(sampleIdx, 0x1000, ctx.bigSampleRepeat, 128, 128);
ctx.bigSampleRepeat = 1;
return 0;
}
/**
* The actor rotate to Twinsen direction (Parameter = -1 (near); = 0 (far))
* @note Opcode @c 0x21
*/
int32 ScriptMove::mFACE_HERO(TwinEEngine *engine, MoveScriptContext &ctx) {
const int16 angle = ToAngle(ctx.stream.readSint16LE());
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::FACE_HERO(%i)", (int)angle);
if (ctx.actor->_flags.bSprite3D) {
return 0;
}
engine->_scene->_currentScriptValue = angle;
if (engine->_scene->_currentScriptValue == -1 && ctx.actor->realAngle.timeValue == 0) {
engine->_scene->_currentScriptValue = engine->_movements->getAngle(ctx.actor->posObj(), engine->_scene->_sceneHero->posObj());
engine->_movements->initRealAngleConst(ctx.actor->_beta, engine->_scene->_currentScriptValue, ctx.actor->_srot, &ctx.actor->realAngle);
ctx.stream.rewind(2);
ctx.stream.writeSint16LE(engine->_scene->_currentScriptValue);
}
if (ctx.actor->_beta != engine->_scene->_currentScriptValue) {
ctx.undo(2);
return 1;
}
engine->_movements->clearRealAngle(ctx.actor);
ctx.stream.rewind(2);
ctx.stream.writeSint16LE(-1);
return 0;
}
/**
* Generate a random angle for the current actor
* @note Opcode @c 0x22
*/
int32 ScriptMove::mANGLE_RND(TwinEEngine *engine, MoveScriptContext &ctx) {
const int16 val1 = ctx.stream.readSint16LE();
const int16 val2 = ctx.stream.readSint16LE();
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::LBAAngles::ANGLE_RND(%i, %i)", (int)val1, (int)val2);
if (ctx.actor->_flags.bSprite3D) {
return 0;
}
engine->_scene->_currentScriptValue = val2;
if (engine->_scene->_currentScriptValue == -1 && ctx.actor->realAngle.timeValue == 0) {
if (engine->getRandomNumber() & 1) {
const int32 newAngle = ctx.actor->_beta + LBAAngles::ANGLE_90 + (ABS(val1) >> 1);
engine->_scene->_currentScriptValue = ClampAngle(newAngle - engine->getRandomNumber(val1));
} else {
const int32 newAngle = ctx.actor->_beta - LBAAngles::ANGLE_90 + (ABS(val1) >> 1);
engine->_scene->_currentScriptValue = ClampAngle(newAngle - engine->getRandomNumber(val1));
}
engine->_movements->initRealAngleConst(ctx.actor->_beta, engine->_scene->_currentScriptValue, ctx.actor->_srot, &ctx.actor->realAngle);
ctx.stream.rewind(2);
ctx.stream.writeSint16LE(engine->_scene->_currentScriptValue);
}
if (ctx.actor->_beta != engine->_scene->_currentScriptValue) {
ctx.undo(4);
return 1;
}
engine->_movements->clearRealAngle(ctx.actor);
ctx.stream.rewind(2);
ctx.stream.writeSint16LE(-1);
return 0;
}
ScriptMove::ScriptMove(TwinEEngine *engine, const ScriptMoveFunction *functionMap, size_t entries) : _engine(engine), _functionMap(functionMap), _functionMapSize(entries) {
}
void ScriptMove::doTrack(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
int32 end = -2;
MoveScriptContext ctx(actorIdx, actor);
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::BEGIN(%i)", actorIdx);
do {
const byte scriptOpcode = ctx.stream.readByte();
if (scriptOpcode < _functionMapSize) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::EXEC(%s, %i)", _functionMap[scriptOpcode].name, actorIdx);
end = _functionMap[scriptOpcode].function(_engine, ctx);
} else {
error("Actor %d with wrong offset/opcode in move script - Offset: %d/%d (opcode: %u)", actorIdx, (int)ctx.stream.pos() - 1, (int)ctx.stream.size(), scriptOpcode);
}
if (end < 0) {
warning("Actor %d Life script [%s] not implemented", actorIdx, _functionMap[scriptOpcode].name);
} else if (end == 1) {
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::BREAK(%i)", actorIdx);
}
if (ctx.actor->_offsetTrack != -1) {
actor->_offsetTrack = ctx.stream.pos();
}
} while (end != 1);
debugC(3, kDebugLevels::kDebugScriptsMove, "MOVE::END(%i)", actorIdx);
}
} // namespace TwinE

View File

@@ -0,0 +1,116 @@
/* 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 TWINE_SCRIPTMOVE_H
#define TWINE_SCRIPTMOVE_H
#include "common/scummsys.h"
#include "twine/scene/actor.h"
namespace TwinE {
struct MoveScriptContext {
int32 actorIdx;
ActorStruct *actor;
int32 bigSampleRepeat = 1;
Common::MemorySeekableReadWriteStream stream;
MoveScriptContext(int32 _actorIdx, ActorStruct *_actor) : actorIdx(_actorIdx), actor(_actor), stream(actor->_ptrTrack, actor->_moveScriptSize) {
assert(actor->_offsetTrack >= 0);
stream.skip(actor->_offsetTrack);
}
void undo(int32 bytes) {
assert(bytes >= 0);
// the additional 1 byte is for the opcode
stream.rewind(bytes + 1);
}
};
/**
* Returns @c -1 Need implementation, @c 0 Condition false, @c 1 - Condition true
*/
typedef int32 ScriptMoveFunc(TwinEEngine *engine, MoveScriptContext &ctx);
struct ScriptMoveFunction {
const char *name;
ScriptMoveFunc *function;
};
class ScriptMove {
private:
TwinEEngine *_engine;
const ScriptMoveFunction* _functionMap;
size_t _functionMapSize;
public:
static int32 mEMPTY(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mEND(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mNOP(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mBODY(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mANIM(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mGOTO_POINT(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mWAIT_ANIM(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mLOOP(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mANGLE(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mPOS_POINT(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mLABEL(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mGOTO(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mSTOP(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mGOTO_SYM_POINT(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mWAIT_NUM_ANIM(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mSAMPLE(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mGOTO_POINT_3D(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mSPEED(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mBACKGROUND(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mWAIT_NUM_SECOND(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mNO_BODY(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mBETA(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mOPEN_GENERIC(TwinEEngine *engine, MoveScriptContext &ctx, int32 angle);
static int32 mOPEN_LEFT(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mOPEN_RIGHT(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mOPEN_UP(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mOPEN_DOWN(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mCLOSE(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mWAIT_DOOR(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mSAMPLE_RND(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mSAMPLE_ALWAYS(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mSAMPLE_STOP(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mPLAY_FLA(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mREPEAT_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mSIMPLE_SAMPLE(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mFACE_HERO(TwinEEngine *engine, MoveScriptContext &ctx);
static int32 mANGLE_RND(TwinEEngine *engine, MoveScriptContext &ctx);
public:
ScriptMove(TwinEEngine *engine, const ScriptMoveFunction* functionMap, size_t entries);
virtual ~ScriptMove() {}
/**
* Process actor move script
* @param actorIdx Current processed actor index
*/
void doTrack(int32 actorIdx);
};
} // namespace TwinE
#endif

Some files were not shown because too many files have changed in this diff Show More