feat: Implement loading LIP files in conversations
This commit is contained in:
parent
746a742744
commit
2566e0d1e4
14 changed files with 270 additions and 34 deletions
|
@ -155,7 +155,9 @@ set(RENDER_HEADERS
|
|||
src/render/image/tgafile.h
|
||||
src/render/image/tpcfile.h
|
||||
src/render/image/txifile.h
|
||||
src/render/lipfile.h
|
||||
src/render/lip/lipanimation.h
|
||||
src/render/lip/lipfile.h
|
||||
src/render/lip/lips.h
|
||||
src/render/mesh/aabb.h
|
||||
src/render/mesh/billboard.h
|
||||
src/render/mesh/cube.h
|
||||
|
@ -191,7 +193,9 @@ set(RENDER_SOURCES
|
|||
src/render/image/tgafile.cpp
|
||||
src/render/image/tpcfile.cpp
|
||||
src/render/image/txifile.cpp
|
||||
src/render/lipfile.cpp
|
||||
src/render/lip/lipanimation.cpp
|
||||
src/render/lip/lipfile.cpp
|
||||
src/render/lip/lips.cpp
|
||||
src/render/mesh/aabb.cpp
|
||||
src/render/mesh/billboard.cpp
|
||||
src/render/mesh/cube.cpp
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "../common/log.h"
|
||||
#include "../common/pathutil.h"
|
||||
#include "../experimental/tor/gr2file.h"
|
||||
#include "../render/lip/lips.h"
|
||||
#include "../render/model/mdlfile.h"
|
||||
#include "../render/model/models.h"
|
||||
#include "../render/textures.h"
|
||||
|
@ -229,6 +230,8 @@ void Game::loadModule(const string &name, string entry) {
|
|||
Scripts::instance().invalidateCache();
|
||||
Blueprints::instance().invalidateCache();
|
||||
SoundSets::instance().invalidateCache();
|
||||
Lips::instance().invalidateCache();
|
||||
|
||||
Resources::instance().loadModule(name);
|
||||
|
||||
if (_module) {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "../../audio/player.h"
|
||||
#include "../../common/log.h"
|
||||
#include "../../gui/control/listbox.h"
|
||||
#include "../../render/lip/lips.h"
|
||||
#include "../../render/model/models.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
|
@ -161,15 +162,16 @@ void Conversation::loadVoiceOver() {
|
|||
}
|
||||
|
||||
// Play current voice over either from Sound or from VO_ResRef
|
||||
shared_ptr<AudioStream> voice;
|
||||
string voiceResRef;
|
||||
if (!_currentEntry->sound.empty()) {
|
||||
voice = AudioFiles::instance().get(_currentEntry->sound);
|
||||
voiceResRef = _currentEntry->sound;
|
||||
}
|
||||
if (!voice && !_currentEntry->voResRef.empty()) {
|
||||
voice = AudioFiles::instance().get(_currentEntry->voResRef);
|
||||
if (voiceResRef.empty() && !_currentEntry->voResRef.empty()) {
|
||||
voiceResRef = _currentEntry->voResRef;
|
||||
}
|
||||
if (voice) {
|
||||
_currentVoice = AudioPlayer::instance().play(voice, AudioType::Voice);
|
||||
if (!voiceResRef.empty()) {
|
||||
_currentVoice = AudioPlayer::instance().play(voiceResRef, AudioType::Voice);
|
||||
_lipAnimation = Lips::instance().get(voiceResRef);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "../../audio/soundhandle.h"
|
||||
#include "../../common/timer.h"
|
||||
#include "../../render/lip/lipanimation.h"
|
||||
#include "../../render/model/model.h"
|
||||
|
||||
#include "../camera/types.h"
|
||||
|
@ -83,6 +84,7 @@ protected:
|
|||
|
||||
private:
|
||||
std::shared_ptr<audio::SoundHandle> _currentVoice;
|
||||
std::shared_ptr<render::LipAnimation> _lipAnimation;
|
||||
Timer _endEntryTimer;
|
||||
float _entryDuration { 0.0f };
|
||||
std::vector<const Dialog::EntryReply *> _replies;
|
||||
|
|
43
src/render/lip/lipanimation.cpp
Normal file
43
src/render/lip/lipanimation.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lipanimation.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
||||
LipAnimation::LipAnimation(vector<Keyframe> keyframes) : _keyframes(move(keyframes)) {
|
||||
}
|
||||
|
||||
const LipAnimation::Keyframe &LipAnimation::getKeyframe(float time) const {
|
||||
if (_keyframes.empty()) {
|
||||
throw logic_error("keyframes is empty");
|
||||
}
|
||||
for (auto &frame : _keyframes) {
|
||||
if (time <= frame.time) return frame;
|
||||
}
|
||||
return _keyframes.back();
|
||||
}
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
44
src/render/lip/lipanimation.h
Normal file
44
src/render/lip/lipanimation.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
||||
class LipAnimation {
|
||||
public:
|
||||
struct Keyframe {
|
||||
float time { 0.0f };
|
||||
uint8_t shape { 0 }; /**< an index into the keyframes of the "talk" animation */
|
||||
};
|
||||
|
||||
LipAnimation(std::vector<Keyframe> keyframes);
|
||||
|
||||
const Keyframe &getKeyframe(float time) const;
|
||||
|
||||
private:
|
||||
std::vector<Keyframe> _keyframes;
|
||||
};
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
|
@ -32,12 +32,15 @@ void LipFile::doLoad() {
|
|||
float length = readFloat();
|
||||
uint32_t entryCount = readUint32();
|
||||
|
||||
vector<LipAnimation::Keyframe> keyframes;
|
||||
for (uint32_t i = 0; i < entryCount; ++i) {
|
||||
Keyframe keyframe;
|
||||
LipAnimation::Keyframe keyframe;
|
||||
keyframe.time = readFloat();
|
||||
keyframe.shape = readByte();
|
||||
_keyframes.push_back(move(keyframe));
|
||||
keyframes.push_back(move(keyframe));
|
||||
}
|
||||
|
||||
_animation = make_shared<LipAnimation>(move(keyframes));
|
||||
}
|
||||
|
||||
} // namespace render
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../resource/format/binfile.h"
|
||||
#include "../../resource/format/binfile.h"
|
||||
|
||||
#include "lipanimation.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
|
@ -25,17 +27,12 @@ namespace render {
|
|||
|
||||
class LipFile : public resource::BinaryFile {
|
||||
public:
|
||||
struct Keyframe {
|
||||
float time { 0.0f };
|
||||
uint8_t shape { 0 };
|
||||
};
|
||||
|
||||
LipFile();
|
||||
|
||||
const std::vector<Keyframe> &keyframes() const { return _keyframes; }
|
||||
std::shared_ptr<LipAnimation> animation() const { return _animation; }
|
||||
|
||||
private:
|
||||
std::vector<Keyframe> _keyframes;
|
||||
std::shared_ptr<LipAnimation> _animation;
|
||||
|
||||
void doLoad() override;
|
||||
};
|
60
src/render/lip/lips.cpp
Normal file
60
src/render/lip/lips.cpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lips.h"
|
||||
|
||||
#include "../../common/streamutil.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
#include "lipfile.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
||||
Lips &Lips::instance() {
|
||||
static Lips instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Lips::invalidateCache() {
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
shared_ptr<LipAnimation> Lips::get(const string &resRef) {
|
||||
auto maybeAnimation = _cache.find(resRef);
|
||||
if (maybeAnimation != _cache.end()) return maybeAnimation->second;
|
||||
|
||||
shared_ptr<LipAnimation> animation;
|
||||
shared_ptr<ByteArray> lipData(Resources::instance().get(resRef, ResourceType::Lip));
|
||||
if (lipData) {
|
||||
LipFile lip;
|
||||
lip.load(wrap(lipData));
|
||||
animation = lip.animation();
|
||||
}
|
||||
|
||||
return _cache.insert(make_pair(resRef, move(animation))).first->second;
|
||||
}
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
46
src/render/lip/lips.h
Normal file
46
src/render/lip/lips.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "lipanimation.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
||||
class Lips {
|
||||
public:
|
||||
static Lips &instance();
|
||||
|
||||
void invalidateCache();
|
||||
|
||||
std::shared_ptr<LipAnimation> get(const std::string &resRef);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::shared_ptr<LipAnimation>> _cache;
|
||||
|
||||
Lips() = default;
|
||||
};
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#include "binfile.h"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
@ -61,7 +63,7 @@ void BinaryFile::checkSignature() {
|
|||
char buf[16];
|
||||
_in->read(buf, _signSize);
|
||||
if (!equal(_sign.begin(), _sign.end(), buf)) {
|
||||
throw runtime_error("Invalid binary file signature");
|
||||
throw runtime_error(str(boost::format("Invalid binary file signature: %s") % string(buf, _signSize)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ static const char kSoundsDirectoryName[] = "streamsounds";
|
|||
static const char kVoiceDirectoryName[] = "streamvoice";
|
||||
static const char kWavesDirectoryName[] = "streamwaves";
|
||||
static const char kTexturePackDirectoryName[] = "texturepacks";
|
||||
static const char kLipsDirectoryName[] = "lips";
|
||||
|
||||
static const char kGUITexturePackFilename[] = "swpc_tex_gui.erf";
|
||||
static const char kTexturePackFilename[] = "swpc_tex_tpa.erf";
|
||||
|
@ -64,10 +65,12 @@ void Resources::init(GameID gameId, const fs::path &gamePath) {
|
|||
|
||||
indexKeyBifFiles();
|
||||
indexTexturePacks();
|
||||
indexAudioFiles();
|
||||
indexOverrideDirectory();
|
||||
indexTalkTable();
|
||||
indexAudioFiles();
|
||||
indexLipModFiles();
|
||||
indexExeFile();
|
||||
indexOverrideDirectory();
|
||||
|
||||
loadModuleNames();
|
||||
}
|
||||
|
||||
|
@ -121,6 +124,28 @@ void Resources::indexAudioFiles() {
|
|||
}
|
||||
}
|
||||
|
||||
void Resources::indexLipModFiles() {
|
||||
static vector<string> kotorMods { "global", "localization" };
|
||||
static vector<string> tslMods { "localization" };
|
||||
|
||||
const vector<string> &mods = _gameId == GameID::KotOR ? kotorMods : tslMods;
|
||||
fs::path lipsPath(getPathIgnoreCase(_gamePath, "lips"));
|
||||
|
||||
for (auto &mod : mods) {
|
||||
fs::path modPath(getPathIgnoreCase(lipsPath, mod + ".mod"));
|
||||
indexErfFile(modPath);
|
||||
}
|
||||
}
|
||||
|
||||
void Resources::indexRimFile(const fs::path &path) {
|
||||
auto rim = make_unique<RimFile>();
|
||||
rim->load(path);
|
||||
|
||||
_providers.push_back(move(rim));
|
||||
|
||||
debug(boost::format("Resources: indexed: %s") % path);
|
||||
}
|
||||
|
||||
void Resources::indexDirectory(const fs::path &path) {
|
||||
auto folder = make_unique<Folder>();
|
||||
folder->load(path);
|
||||
|
@ -192,11 +217,15 @@ void Resources::loadModule(const string &name) {
|
|||
_transientProviders.clear();
|
||||
|
||||
fs::path modulesPath(getPathIgnoreCase(_gamePath, kModulesDirectoryName));
|
||||
fs::path rimPath(getPathIgnoreCase(modulesPath, name + ".rim"));
|
||||
fs::path rimsPath(getPathIgnoreCase(modulesPath, name + "_s.rim"));
|
||||
fs::path moduleRimPath(getPathIgnoreCase(modulesPath, name + ".rim"));
|
||||
fs::path moduleRimSPath(getPathIgnoreCase(modulesPath, name + "_s.rim"));
|
||||
|
||||
indexTransientRimFile(rimPath);
|
||||
indexTransientRimFile(rimsPath);
|
||||
fs::path lipsPath(getPathIgnoreCase(_gamePath, kLipsDirectoryName));
|
||||
fs::path lipModPath(getPathIgnoreCase(lipsPath, name + "_loc.mod"));
|
||||
|
||||
indexTransientRimFile(moduleRimPath);
|
||||
indexTransientRimFile(moduleRimSPath);
|
||||
indexTransientErfFile(lipModPath);
|
||||
|
||||
if (_gameId == GameID::TSL) {
|
||||
fs::path dlgPath(getPathIgnoreCase(modulesPath, name + "_dlg.erf"));
|
||||
|
|
|
@ -112,16 +112,19 @@ private:
|
|||
|
||||
Resources &operator=(const Resources &) = delete;
|
||||
|
||||
void indexAudioFiles();
|
||||
void indexDirectory(const boost::filesystem::path &path);
|
||||
void indexErfFile(const boost::filesystem::path &path);
|
||||
void indexExeFile();
|
||||
void indexKeyBifFiles();
|
||||
void indexOverrideDirectory();
|
||||
void indexTalkTable();
|
||||
void indexTexturePacks();
|
||||
void indexTalkTable();
|
||||
void indexAudioFiles();
|
||||
void indexLipModFiles();
|
||||
void indexExeFile();
|
||||
void indexOverrideDirectory();
|
||||
|
||||
void indexErfFile(const boost::filesystem::path &path);
|
||||
void indexTransientErfFile(const boost::filesystem::path &path);
|
||||
void indexRimFile(const boost::filesystem::path &path);
|
||||
void indexTransientRimFile(const boost::filesystem::path &path);
|
||||
void indexDirectory(const boost::filesystem::path &path);
|
||||
|
||||
void invalidateCache();
|
||||
void loadModuleNames();
|
||||
|
|
|
@ -104,9 +104,7 @@ void GffTool::toJSON(const fs::path &path, const fs::path &destPath) {
|
|||
}
|
||||
|
||||
bool GffTool::supports(Operation operation, const fs::path &target) const {
|
||||
return
|
||||
!fs::is_directory(target) &&
|
||||
operation == Operation::ToJSON;
|
||||
return !fs::is_directory(target) && operation == Operation::ToJSON;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
|
Loading…
Reference in a new issue