mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 07:45:39 +00:00
Merge branch 'master' into 'sort_me_baby_one_more_time'
# Conflicts: # components/nif/niffile.cpp
This commit is contained in:
commit
b011809056
264 changed files with 22298 additions and 1234 deletions
|
@ -116,11 +116,12 @@ Ubuntu_GCC_tests:
|
|||
Ubuntu_GCC_tests_Debug:
|
||||
extends: Ubuntu_GCC
|
||||
cache:
|
||||
key: Ubuntu_GCC_tests_Debug.v1
|
||||
key: Ubuntu_GCC_tests_Debug.v2
|
||||
variables:
|
||||
CCACHE_SIZE: 1G
|
||||
BUILD_TESTS_ONLY: 1
|
||||
CMAKE_BUILD_TYPE: Debug
|
||||
CMAKE_CXX_FLAGS_DEBUG: -g -O0 -D_GLIBCXX_ASSERTIONS
|
||||
artifacts:
|
||||
paths: []
|
||||
expire_in: 1 minute
|
||||
|
|
|
@ -249,6 +249,7 @@ Documentation
|
|||
Joakim Berg (lysol90)
|
||||
Ryan Tucker (Ravenwing)
|
||||
sir_herrbatka
|
||||
David Nagy (zuzaman)
|
||||
|
||||
Packagers
|
||||
---------
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
Bug #4700: Editor: Incorrect command implementation
|
||||
Bug #4744: Invisible particles must still be processed
|
||||
Bug #4949: Incorrect particle lighting
|
||||
Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations
|
||||
Bug #5088: Sky abruptly changes direction during certain weather transitions
|
||||
Bug #5100: Persuasion doesn't always clamp the resulting disposition
|
||||
Bug #5120: Scripted object spawning updates physics system
|
||||
|
@ -274,6 +275,7 @@
|
|||
Bug #6142: Groundcover plugins change cells flags
|
||||
Bug #6276: Deleted groundcover instances are not deleted in game
|
||||
Bug #6294: Game crashes with empty pathgrid
|
||||
Bug #6606: Quests with multiple IDs cannot always be restarted
|
||||
Feature #390: 3rd person look "over the shoulder"
|
||||
Feature #832: OpenMW-CS: Handle deleted references
|
||||
Feature #1536: Show more information about level on menu
|
||||
|
|
|
@ -484,9 +484,9 @@ int clone(Arguments& info)
|
|||
if (i <= 0)
|
||||
break;
|
||||
|
||||
const ESM::NAME& typeName = record->getType();
|
||||
const ESM::NAME typeName = record->getType();
|
||||
|
||||
esm.startRecord(typeName.toString(), record->getFlags());
|
||||
esm.startRecord(typeName, record->getFlags());
|
||||
|
||||
record->save(esm);
|
||||
if (typeName.toInt() == ESM::REC_CELL) {
|
||||
|
@ -498,7 +498,7 @@ int clone(Arguments& info)
|
|||
}
|
||||
}
|
||||
|
||||
esm.endRecord(typeName.toString());
|
||||
esm.endRecord(typeName);
|
||||
|
||||
saved++;
|
||||
int perc = recordCount == 0 ? 100 : (int)((saved / (float)recordCount)*100);
|
||||
|
|
|
@ -663,49 +663,51 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::p
|
|||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
|
||||
line = encoder.getUtf8(line);
|
||||
std::string_view utf8 = encoder.getUtf8(line);
|
||||
|
||||
// unify Unix-style and Windows file ending
|
||||
if (!(line.empty()) && (line[line.length()-1]) == '\r') {
|
||||
line = line.substr(0, line.length()-1);
|
||||
if (!(utf8.empty()) && (utf8[utf8.length()-1]) == '\r') {
|
||||
utf8 = utf8.substr(0, utf8.length()-1);
|
||||
}
|
||||
|
||||
if(line.empty()) {
|
||||
if(utf8.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(line[0] == '[') {
|
||||
int pos = static_cast<int>(line.find(']'));
|
||||
if(utf8[0] == '[') {
|
||||
int pos = static_cast<int>(utf8.find(']'));
|
||||
if(pos < 2) {
|
||||
std::cout << "Warning: ini file wrongly formatted (" << line << "). Line ignored." << std::endl;
|
||||
std::cout << "Warning: ini file wrongly formatted (" << utf8 << "). Line ignored." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
section = line.substr(1, line.find(']')-1);
|
||||
section = utf8.substr(1, utf8.find(']')-1);
|
||||
continue;
|
||||
}
|
||||
|
||||
int comment_pos = static_cast<int>(line.find(';'));
|
||||
int comment_pos = static_cast<int>(utf8.find(';'));
|
||||
if(comment_pos > 0) {
|
||||
line = line.substr(0,comment_pos);
|
||||
utf8 = utf8.substr(0,comment_pos);
|
||||
}
|
||||
|
||||
int pos = static_cast<int>(line.find('='));
|
||||
int pos = static_cast<int>(utf8.find('='));
|
||||
if(pos < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string key(section + ":" + line.substr(0,pos));
|
||||
std::string value(line.substr(pos+1));
|
||||
std::string key(section + ":" + std::string(utf8.substr(0, pos)));
|
||||
const std::string_view value(utf8.substr(pos+1));
|
||||
if(value.empty()) {
|
||||
std::cout << "Warning: ignored empty value for key '" << key << "'." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(map.find(key) == map.end()) {
|
||||
map.insert( std::make_pair (key, std::vector<std::string>() ) );
|
||||
}
|
||||
map[key].push_back(value);
|
||||
auto it = map.find(key);
|
||||
|
||||
if (it == map.end())
|
||||
it = map.emplace_hint(it, std::move(key), std::vector<std::string>());
|
||||
|
||||
it->second.push_back(std::string(value));
|
||||
}
|
||||
|
||||
return map;
|
||||
|
|
|
@ -178,7 +178,7 @@ namespace NavMeshTool
|
|||
static_cast<btScalar>(cellPosition.y() * ESM::Land::REAL_SIZE),
|
||||
minHeight
|
||||
);
|
||||
aabb.m_min = btVector3(
|
||||
aabb.m_max = btVector3(
|
||||
static_cast<btScalar>((cellPosition.x() + 1) * ESM::Land::REAL_SIZE),
|
||||
static_cast<btScalar>((cellPosition.y() + 1) * ESM::Land::REAL_SIZE),
|
||||
maxHeight
|
||||
|
|
|
@ -370,7 +370,7 @@ int CS::Editor::run()
|
|||
else
|
||||
{
|
||||
ESM::ESMReader fileReader;
|
||||
ToUTF8::Utf8Encoder encoder = ToUTF8::calculateEncoding(mEncodingName);
|
||||
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncodingName));
|
||||
fileReader.setEncoder(&encoder);
|
||||
fileReader.open(mFileToLoad.string());
|
||||
|
||||
|
|
|
@ -358,6 +358,8 @@ namespace MWBase
|
|||
|
||||
virtual void onDeleteCustomData(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual void asyncPrepareSaveMap() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -293,7 +293,7 @@ namespace MWBase
|
|||
virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec) = 0;
|
||||
///< @return an updated Ptr
|
||||
|
||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
|
||||
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale, bool force = false) = 0;
|
||||
|
||||
virtual void rotateObject(const MWWorld::Ptr& ptr, const osg::Vec3f& rot, RotationFlags flags = RotationFlag_inverseOrder) = 0;
|
||||
|
||||
|
|
|
@ -676,11 +676,8 @@ namespace MWDialogue
|
|||
{
|
||||
ESM::DialogueState state;
|
||||
|
||||
for (std::set<std::string>::const_iterator iter (mKnownTopics.begin());
|
||||
iter!=mKnownTopics.end(); ++iter)
|
||||
{
|
||||
state.mKnownTopics.push_back (*iter);
|
||||
}
|
||||
state.mKnownTopics.reserve(mKnownTopics.size());
|
||||
std::copy(mKnownTopics.begin(), mKnownTopics.end(), std::back_inserter(state.mKnownTopics));
|
||||
|
||||
state.mChangedFactionReaction = mChangedFactionReaction;
|
||||
|
||||
|
|
|
@ -257,11 +257,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
|
|||
case SelectWrapper::Function_PcHealthPercent:
|
||||
{
|
||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
|
||||
float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() /
|
||||
player.getClass().getCreatureStats (player).getHealth().getModified();
|
||||
|
||||
return select.selectCompare (static_cast<int>(ratio*100));
|
||||
return select.selectCompare(static_cast<int>(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100));
|
||||
}
|
||||
|
||||
case SelectWrapper::Function_PcDynamicStat:
|
||||
|
@ -276,10 +272,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c
|
|||
|
||||
case SelectWrapper::Function_HealthPercent:
|
||||
{
|
||||
float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() /
|
||||
mActor.getClass().getCreatureStats (mActor).getHealth().getModified();
|
||||
|
||||
return select.selectCompare (static_cast<int>(ratio*100));
|
||||
return select.selectCompare(static_cast<int>(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100));
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <components/esm3/queststate.hpp>
|
||||
#include <components/esm3/journalentry.hpp>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
|
@ -93,7 +95,16 @@ namespace MWDialogue
|
|||
StampedJournalEntry entry = StampedJournalEntry::makeFromQuest (id, index, actor);
|
||||
|
||||
Quest& quest = getQuest (id);
|
||||
quest.addEntry (entry); // we are doing slicing on purpose here
|
||||
if(quest.addEntry(entry)) // we are doing slicing on purpose here
|
||||
{
|
||||
// Restart all "other" quests with the same name as well
|
||||
std::string name = quest.getName();
|
||||
for(auto& it : mQuests)
|
||||
{
|
||||
if(it.second.isFinished() && Misc::StringUtils::ciEqual(it.second.getName(), name))
|
||||
it.second.setFinished(false);
|
||||
}
|
||||
}
|
||||
|
||||
// there is no need to show empty entries in journal
|
||||
if (!entry.getText().empty())
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "quest.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <components/esm3/queststate.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
@ -50,42 +52,33 @@ namespace MWDialogue
|
|||
return mFinished;
|
||||
}
|
||||
|
||||
void Quest::addEntry (const JournalEntry& entry)
|
||||
void Quest::setFinished(bool finished)
|
||||
{
|
||||
int index = -1;
|
||||
mFinished = finished;
|
||||
}
|
||||
|
||||
bool Quest::addEntry (const JournalEntry& entry)
|
||||
{
|
||||
const ESM::Dialogue *dialogue =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Dialogue>().find (entry.mTopic);
|
||||
|
||||
for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin());
|
||||
iter!=dialogue->mInfo.end(); ++iter)
|
||||
if (iter->mId == entry.mInfoId)
|
||||
{
|
||||
index = iter->mData.mJournalIndex;
|
||||
break;
|
||||
}
|
||||
auto info = std::find_if(dialogue->mInfo.begin(), dialogue->mInfo.end(), [&](const auto& info) { return info.mId == entry.mInfoId; });
|
||||
|
||||
if (index==-1)
|
||||
if (info == dialogue->mInfo.end() || info->mData.mJournalIndex == -1)
|
||||
throw std::runtime_error ("unknown journal entry for topic " + mTopic);
|
||||
|
||||
for (auto &info : dialogue->mInfo)
|
||||
{
|
||||
if (info.mData.mJournalIndex == index
|
||||
&& (info.mQuestStatus == ESM::DialInfo::QS_Finished || info.mQuestStatus == ESM::DialInfo::QS_Restart))
|
||||
{
|
||||
mFinished = (info.mQuestStatus == ESM::DialInfo::QS_Finished);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (info->mQuestStatus == ESM::DialInfo::QS_Finished || info->mQuestStatus == ESM::DialInfo::QS_Restart)
|
||||
mFinished = info->mQuestStatus == ESM::DialInfo::QS_Finished;
|
||||
|
||||
if (index > mIndex)
|
||||
mIndex = index;
|
||||
if (info->mData.mJournalIndex > mIndex)
|
||||
mIndex = info->mData.mJournalIndex;
|
||||
|
||||
for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter)
|
||||
if (iter->mInfoId==entry.mInfoId)
|
||||
return;
|
||||
return info->mQuestStatus == ESM::DialInfo::QS_Restart;
|
||||
|
||||
mEntries.push_back (entry); // we want slicing here
|
||||
return info->mQuestStatus == ESM::DialInfo::QS_Restart;
|
||||
}
|
||||
|
||||
void Quest::write (ESM::QuestState& state) const
|
||||
|
|
|
@ -33,9 +33,10 @@ namespace MWDialogue
|
|||
///< Calling this function with a non-existent index will throw an exception.
|
||||
|
||||
bool isFinished() const;
|
||||
void setFinished(bool finished);
|
||||
|
||||
void addEntry (const JournalEntry& entry) override;
|
||||
///< Add entry and adjust index accordingly.
|
||||
bool addEntry (const JournalEntry& entry) override;
|
||||
///< Add entry and adjust index accordingly. Returns true if the quest should be restarted.
|
||||
///
|
||||
/// \note Redundant entries are ignored, but the index is still adjusted.
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace MWDialogue
|
|||
Topic::~Topic()
|
||||
{}
|
||||
|
||||
void Topic::addEntry (const JournalEntry& entry)
|
||||
bool Topic::addEntry (const JournalEntry& entry)
|
||||
{
|
||||
if (entry.mTopic!=mTopic)
|
||||
throw std::runtime_error ("topic does not match: " + mTopic);
|
||||
|
@ -27,10 +27,11 @@ namespace MWDialogue
|
|||
for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it)
|
||||
{
|
||||
if (it->mInfoId == entry.mInfoId)
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
mEntries.push_back (entry); // we want slicing here
|
||||
return false;
|
||||
}
|
||||
|
||||
void Topic::insertEntry (const ESM::JournalEntry& entry)
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace MWDialogue
|
|||
|
||||
virtual ~Topic();
|
||||
|
||||
virtual void addEntry (const JournalEntry& entry);
|
||||
virtual bool addEntry (const JournalEntry& entry);
|
||||
///< Add entry
|
||||
///
|
||||
/// \note Redundant entries are ignored.
|
||||
|
|
|
@ -608,7 +608,7 @@ namespace MWGui
|
|||
mEnemyHealth->setProgressRange(100);
|
||||
// Health is usually cast to int before displaying. Actors die whenever they are < 1 health.
|
||||
// Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :)
|
||||
mEnemyHealth->setProgressPosition(static_cast<size_t>(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100));
|
||||
mEnemyHealth->setProgressPosition(static_cast<size_t>(stats.getHealth().getRatio() * 100));
|
||||
|
||||
static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fNPCHealthBarFade")->mValue.getFloat();
|
||||
if (fNPCHealthBarFade > 0.f)
|
||||
|
|
|
@ -759,7 +759,7 @@ namespace MWGui
|
|||
, mGlobal(Settings::Manager::getBool("global", "Map"))
|
||||
, mEventBoxGlobal(nullptr)
|
||||
, mEventBoxLocal(nullptr)
|
||||
, mGlobalMapRender(new MWRender::GlobalMap(localMapRender->getRoot(), workQueue))
|
||||
, mGlobalMapRender(std::make_unique<MWRender::GlobalMap>(localMapRender->getRoot(), workQueue))
|
||||
, mEditNoteDialog()
|
||||
{
|
||||
static bool registered = false;
|
||||
|
@ -1028,7 +1028,6 @@ namespace MWGui
|
|||
|
||||
MapWindow::~MapWindow()
|
||||
{
|
||||
delete mGlobalMapRender;
|
||||
}
|
||||
|
||||
void MapWindow::setCellName(const std::string& cellName)
|
||||
|
@ -1357,6 +1356,11 @@ namespace MWGui
|
|||
marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed);
|
||||
}
|
||||
|
||||
void MapWindow::asyncPrepareSaveMap()
|
||||
{
|
||||
mGlobalMapRender->asyncWritePng();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
EditNoteDialog::EditNoteDialog()
|
||||
|
|
|
@ -260,6 +260,8 @@ namespace MWGui
|
|||
void write (ESM::ESMWriter& writer, Loading::Listener& progress);
|
||||
void readRecord (ESM::ESMReader& reader, uint32_t type);
|
||||
|
||||
void asyncPrepareSaveMap();
|
||||
|
||||
private:
|
||||
void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
|
||||
void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id);
|
||||
|
@ -304,7 +306,7 @@ namespace MWGui
|
|||
MyGUI::Button* mEventBoxLocal;
|
||||
|
||||
float mGlobalMapZoom = 1.0f;
|
||||
MWRender::GlobalMap* mGlobalMapRender;
|
||||
std::unique_ptr<MWRender::GlobalMap> mGlobalMapRender;
|
||||
|
||||
struct MapMarkerType
|
||||
{
|
||||
|
|
|
@ -399,7 +399,7 @@ namespace MWGui
|
|||
skillWidget = mSkillList->createWidget<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default,
|
||||
std::string("Skill") + MyGUI::utility::toString(i));
|
||||
skillWidget->setSkillNumber(skillId);
|
||||
skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast<float>(race->mData.mBonus[i].mBonus)));
|
||||
skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast<float>(race->mData.mBonus[i].mBonus), 0.f));
|
||||
ToolTips::createSkillToolTip(skillWidget, skillId);
|
||||
|
||||
|
||||
|
|
|
@ -179,7 +179,7 @@ namespace MWGui
|
|||
void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat<float>& value)
|
||||
{
|
||||
int current = static_cast<int>(value.getCurrent());
|
||||
int modified = static_cast<int>(value.getModified());
|
||||
int modified = static_cast<int>(value.getModified(false));
|
||||
|
||||
// Fatigue can be negative
|
||||
if (id != "FBar")
|
||||
|
|
|
@ -181,8 +181,6 @@ namespace MWGui
|
|||
public:
|
||||
MWSpell();
|
||||
|
||||
typedef MWMechanics::Stat<int> SpellValue;
|
||||
|
||||
void setSpellId(const std::string &id);
|
||||
|
||||
/**
|
||||
|
@ -215,8 +213,6 @@ namespace MWGui
|
|||
public:
|
||||
MWEffectList();
|
||||
|
||||
typedef MWMechanics::Stat<int> EnchantmentValue;
|
||||
|
||||
enum EffectFlags
|
||||
{
|
||||
EF_NoTarget = 0x01, // potions have no target (target is always the player)
|
||||
|
|
|
@ -2263,4 +2263,9 @@ namespace MWGui
|
|||
for(auto* window : mWindows)
|
||||
window->onDeleteCustomData(ptr);
|
||||
}
|
||||
|
||||
void WindowManager::asyncPrepareSaveMap()
|
||||
{
|
||||
mMap->asyncPrepareSaveMap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -392,6 +392,8 @@ namespace MWGui
|
|||
void onDeleteCustomData(const MWWorld::Ptr& ptr) override;
|
||||
void forceLootMode(const MWWorld::Ptr& ptr) override;
|
||||
|
||||
void asyncPrepareSaveMap() override;
|
||||
|
||||
private:
|
||||
unsigned int mOldUpdateMask; unsigned int mOldCullMask;
|
||||
|
||||
|
|
|
@ -113,15 +113,12 @@ namespace MWLua
|
|||
{
|
||||
const MWWorld::Ptr& ptr = self.ptr();
|
||||
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
||||
std::list<std::shared_ptr<AiPackage>>& list = ai.getUnderlyingList();
|
||||
for (auto it = list.begin(); it != list.end();)
|
||||
|
||||
ai.erasePackagesIf([&](auto& entry)
|
||||
{
|
||||
bool keep = LuaUtil::call(callback, *it).get<bool>();
|
||||
if (keep)
|
||||
++it;
|
||||
else
|
||||
it = list.erase(it);
|
||||
}
|
||||
bool keep = LuaUtil::call(callback, entry).template get<bool>();
|
||||
return !keep;
|
||||
});
|
||||
};
|
||||
selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target)
|
||||
{
|
||||
|
|
|
@ -73,23 +73,20 @@ bool isCommanded(const MWWorld::Ptr& actor)
|
|||
// Check for command effects having ended and remove package if necessary
|
||||
void adjustCommandedActor (const MWWorld::Ptr& actor)
|
||||
{
|
||||
if (!isCommanded(actor))
|
||||
return;
|
||||
|
||||
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
bool hasCommandPackage = false;
|
||||
|
||||
auto it = stats.getAiSequence().begin();
|
||||
for (; it != stats.getAiSequence().end(); ++it)
|
||||
stats.getAiSequence().erasePackageIf([](auto& entry)
|
||||
{
|
||||
if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow &&
|
||||
static_cast<const MWMechanics::AiFollow*>(it->get())->isCommanded())
|
||||
if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow &&
|
||||
static_cast<const MWMechanics::AiFollow*>(entry.get())->isCommanded())
|
||||
{
|
||||
hasCommandPackage = true;
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCommanded(actor) && hasCommandPackage)
|
||||
stats.getAiSequence().erase(it);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka)
|
||||
|
|
|
@ -476,8 +476,7 @@ namespace MWMechanics
|
|||
static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat();
|
||||
static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat();
|
||||
|
||||
float healthPercentage = (stats.getHealth().getModified() == 0.0f)
|
||||
? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified();
|
||||
float healthPercentage = stats.getHealth().getRatio(false);
|
||||
float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult;
|
||||
|
||||
static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "aisequence.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/esm3/aisequence.hpp>
|
||||
|
@ -29,6 +30,9 @@ void AiSequence::copy (const AiSequence& sequence)
|
|||
// We need to keep an AiWander storage, if present - it has a state machine.
|
||||
// Not sure about another temporary storages
|
||||
sequence.mAiState.copy<AiWanderStorage>(mAiState);
|
||||
|
||||
mNumCombatPackages = sequence.mNumCombatPackages;
|
||||
mNumPursuitPackages = sequence.mNumPursuitPackages;
|
||||
}
|
||||
|
||||
AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {}
|
||||
|
@ -58,6 +62,28 @@ AiSequence::~AiSequence()
|
|||
clear();
|
||||
}
|
||||
|
||||
void AiSequence::onPackageAdded(const AiPackage& package)
|
||||
{
|
||||
if (package.getTypeId() == AiPackageTypeId::Combat)
|
||||
mNumCombatPackages++;
|
||||
else if (package.getTypeId() == AiPackageTypeId::Pursue)
|
||||
mNumPursuitPackages++;
|
||||
|
||||
assert(mNumCombatPackages >= 0);
|
||||
assert(mNumPursuitPackages >= 0);
|
||||
}
|
||||
|
||||
void AiSequence::onPackageRemoved(const AiPackage& package)
|
||||
{
|
||||
if (package.getTypeId() == AiPackageTypeId::Combat)
|
||||
mNumCombatPackages--;
|
||||
else if (package.getTypeId() == AiPackageTypeId::Pursue)
|
||||
mNumPursuitPackages--;
|
||||
|
||||
assert(mNumCombatPackages >= 0);
|
||||
assert(mNumPursuitPackages >= 0);
|
||||
}
|
||||
|
||||
AiPackageTypeId AiSequence::getTypeId() const
|
||||
{
|
||||
if (mPackages.empty())
|
||||
|
@ -87,32 +113,30 @@ bool AiSequence::getCombatTargets(std::vector<MWWorld::Ptr> &targetActors) const
|
|||
return !targetActors.empty();
|
||||
}
|
||||
|
||||
void AiSequence::erase(std::list<std::shared_ptr<AiPackage>>::const_iterator package)
|
||||
AiPackages::iterator AiSequence::erase(AiPackages::iterator package)
|
||||
{
|
||||
// Not sure if manually terminated packages should trigger mDone, probably not?
|
||||
for(auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||
{
|
||||
if (package == it)
|
||||
{
|
||||
mPackages.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("can't find package to erase");
|
||||
auto& ptr = *package;
|
||||
onPackageRemoved(*ptr);
|
||||
|
||||
return mPackages.erase(package);
|
||||
}
|
||||
|
||||
bool AiSequence::isInCombat() const
|
||||
{
|
||||
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||
{
|
||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return mNumCombatPackages > 0;
|
||||
}
|
||||
|
||||
bool AiSequence::isInPursuit() const
|
||||
{
|
||||
return mNumPursuitPackages > 0;
|
||||
}
|
||||
|
||||
bool AiSequence::isEngagedWithActor() const
|
||||
{
|
||||
if (!isInCombat())
|
||||
return false;
|
||||
|
||||
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||
{
|
||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
||||
|
@ -127,16 +151,18 @@ bool AiSequence::isEngagedWithActor() const
|
|||
|
||||
bool AiSequence::hasPackage(AiPackageTypeId typeId) const
|
||||
{
|
||||
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||
auto it = std::find_if(mPackages.begin(), mPackages.end(), [typeId](const auto& package)
|
||||
{
|
||||
if ((*it)->getTypeId() == typeId)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return package->getTypeId() == typeId;
|
||||
});
|
||||
return it != mPackages.end();
|
||||
}
|
||||
|
||||
bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
|
||||
{
|
||||
if (!isInCombat())
|
||||
return false;
|
||||
|
||||
for (auto it = mPackages.begin(); it != mPackages.end(); ++it)
|
||||
{
|
||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
||||
|
@ -148,27 +174,31 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: use std::list::remove_if for all these methods when we switch to C++20
|
||||
void AiSequence::stopCombat()
|
||||
void AiSequence::removePackagesById(AiPackageTypeId id)
|
||||
{
|
||||
for(auto it = mPackages.begin(); it != mPackages.end(); )
|
||||
for (auto it = mPackages.begin(); it != mPackages.end(); )
|
||||
{
|
||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat)
|
||||
if ((*it)->getTypeId() == id)
|
||||
{
|
||||
it = mPackages.erase(it);
|
||||
it = erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void AiSequence::stopCombat()
|
||||
{
|
||||
removePackagesById(AiPackageTypeId::Combat);
|
||||
}
|
||||
|
||||
void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets)
|
||||
{
|
||||
for(auto it = mPackages.begin(); it != mPackages.end(); )
|
||||
{
|
||||
if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end())
|
||||
{
|
||||
it = mPackages.erase(it);
|
||||
it = erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
|
@ -177,15 +207,7 @@ void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets)
|
|||
|
||||
void AiSequence::stopPursuit()
|
||||
{
|
||||
for(auto it = mPackages.begin(); it != mPackages.end(); )
|
||||
{
|
||||
if ((*it)->getTypeId() == AiPackageTypeId::Pursue)
|
||||
{
|
||||
it = mPackages.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
removePackagesById(AiPackageTypeId::Pursue);
|
||||
}
|
||||
|
||||
bool AiSequence::isPackageDone() const
|
||||
|
@ -204,112 +226,117 @@ namespace
|
|||
|
||||
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange)
|
||||
{
|
||||
if(actor != getPlayer())
|
||||
if (actor == getPlayer())
|
||||
{
|
||||
if (mPackages.empty())
|
||||
// Players don't use this.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPackages.empty())
|
||||
{
|
||||
mLastAiPackage = AiPackageTypeId::None;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* package = mPackages.front().get();
|
||||
if (!package->alwaysActive() && outOfRange)
|
||||
return;
|
||||
|
||||
auto packageTypeId = package->getTypeId();
|
||||
// workaround ai packages not being handled as in the vanilla engine
|
||||
if (isActualAiPackage(packageTypeId))
|
||||
mLastAiPackage = packageTypeId;
|
||||
// if active package is combat one, choose nearest target
|
||||
if (packageTypeId == AiPackageTypeId::Combat)
|
||||
{
|
||||
auto itActualCombat = mPackages.end();
|
||||
|
||||
float nearestDist = std::numeric_limits<float>::max();
|
||||
osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3();
|
||||
|
||||
float bestRating = 0.f;
|
||||
|
||||
for (auto it = mPackages.begin(); it != mPackages.end();)
|
||||
{
|
||||
mLastAiPackage = AiPackageTypeId::None;
|
||||
return;
|
||||
}
|
||||
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
|
||||
|
||||
auto packageIt = mPackages.begin();
|
||||
MWMechanics::AiPackage* package = packageIt->get();
|
||||
if (!package->alwaysActive() && outOfRange)
|
||||
return;
|
||||
MWWorld::Ptr target = (*it)->getTarget();
|
||||
|
||||
auto packageTypeId = package->getTypeId();
|
||||
// workaround ai packages not being handled as in the vanilla engine
|
||||
if (isActualAiPackage(packageTypeId))
|
||||
mLastAiPackage = packageTypeId;
|
||||
// if active package is combat one, choose nearest target
|
||||
if (packageTypeId == AiPackageTypeId::Combat)
|
||||
{
|
||||
auto itActualCombat = mPackages.end();
|
||||
|
||||
float nearestDist = std::numeric_limits<float>::max();
|
||||
osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3();
|
||||
|
||||
float bestRating = 0.f;
|
||||
|
||||
for (auto it = mPackages.begin(); it != mPackages.end();)
|
||||
// target disappeared (e.g. summoned creatures)
|
||||
if (target.isEmpty())
|
||||
{
|
||||
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
|
||||
|
||||
MWWorld::Ptr target = (*it)->getTarget();
|
||||
|
||||
// target disappeared (e.g. summoned creatures)
|
||||
if (target.isEmpty())
|
||||
{
|
||||
it = mPackages.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
float rating = MWMechanics::getBestActionRating(actor, target);
|
||||
|
||||
const ESM::Position &targetPos = target.getRefData().getPosition();
|
||||
|
||||
float distTo = (targetPos.asVec3() - vActorPos).length2();
|
||||
|
||||
// Small threshold for changing target
|
||||
if (it == mPackages.begin())
|
||||
distTo = std::max(0.f, distTo - 2500.f);
|
||||
|
||||
// if a target has higher priority than current target or has same priority but closer
|
||||
if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating))
|
||||
{
|
||||
nearestDist = distTo;
|
||||
itActualCombat = it;
|
||||
bestRating = rating;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!mPackages.empty());
|
||||
|
||||
if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat)
|
||||
{
|
||||
assert(itActualCombat != mPackages.end());
|
||||
// move combat package with nearest target to the front
|
||||
mPackages.splice(mPackages.begin(), mPackages, itActualCombat);
|
||||
}
|
||||
|
||||
packageIt = mPackages.begin();
|
||||
package = packageIt->get();
|
||||
packageTypeId = package->getTypeId();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (package->execute(actor, characterController, mAiState, duration))
|
||||
{
|
||||
// Put repeating noncombat AI packages on the end of the stack so they can be used again
|
||||
if (isActualAiPackage(packageTypeId) && package->getRepeat())
|
||||
{
|
||||
package->reset();
|
||||
mPackages.push_back(package->clone());
|
||||
}
|
||||
// To account for the rare case where AiPackage::execute() queued another AI package
|
||||
// (e.g. AiPursue executing a dialogue script that uses startCombat)
|
||||
mPackages.erase(packageIt);
|
||||
if (isActualAiPackage(packageTypeId))
|
||||
mDone = true;
|
||||
it = erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
mDone = false;
|
||||
float rating = MWMechanics::getBestActionRating(actor, target);
|
||||
|
||||
const ESM::Position &targetPos = target.getRefData().getPosition();
|
||||
|
||||
float distTo = (targetPos.asVec3() - vActorPos).length2();
|
||||
|
||||
// Small threshold for changing target
|
||||
if (it == mPackages.begin())
|
||||
distTo = std::max(0.f, distTo - 2500.f);
|
||||
|
||||
// if a target has higher priority than current target or has same priority but closer
|
||||
if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating))
|
||||
{
|
||||
nearestDist = distTo;
|
||||
itActualCombat = it;
|
||||
bestRating = rating;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
|
||||
if (mPackages.empty())
|
||||
return;
|
||||
|
||||
if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat)
|
||||
{
|
||||
Log(Debug::Error) << "Error during AiSequence::execute: " << e.what();
|
||||
assert(itActualCombat != mPackages.end());
|
||||
// move combat package with nearest target to the front
|
||||
std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat));
|
||||
}
|
||||
|
||||
package = mPackages.front().get();
|
||||
packageTypeId = package->getTypeId();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (package->execute(actor, characterController, mAiState, duration))
|
||||
{
|
||||
// Put repeating non-combat AI packages on the end of the stack so they can be used again
|
||||
if (isActualAiPackage(packageTypeId) && package->getRepeat())
|
||||
{
|
||||
package->reset();
|
||||
mPackages.push_back(package->clone());
|
||||
}
|
||||
|
||||
// To account for the rare case where AiPackage::execute() queued another AI package
|
||||
// (e.g. AiPursue executing a dialogue script that uses startCombat)
|
||||
erase(mPackages.begin());
|
||||
if (isActualAiPackage(packageTypeId))
|
||||
mDone = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mDone = false;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "Error during AiSequence::execute: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void AiSequence::clear()
|
||||
{
|
||||
mPackages.clear();
|
||||
mNumCombatPackages = 0;
|
||||
mNumPursuitPackages = 0;
|
||||
}
|
||||
|
||||
void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther)
|
||||
|
@ -353,7 +380,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
|
|||
{
|
||||
if((*it)->canCancel())
|
||||
{
|
||||
it = mPackages.erase(it);
|
||||
it = erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
|
@ -373,11 +400,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
|
|||
|
||||
if((*it)->getPriority() <= package.getPriority())
|
||||
{
|
||||
onPackageAdded(package);
|
||||
mPackages.insert(it, package.clone());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onPackageAdded(package);
|
||||
mPackages.push_back(package.clone());
|
||||
|
||||
// Make sure that temporary storage is empty
|
||||
|
@ -435,6 +464,8 @@ void AiSequence::fill(const ESM::AIPackageList &list)
|
|||
ESM::AITarget data = esmPackage.mTarget;
|
||||
package = std::make_unique<MWMechanics::AiFollow>(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0);
|
||||
}
|
||||
|
||||
onPackageAdded(*package);
|
||||
mPackages.push_back(std::move(package));
|
||||
}
|
||||
}
|
||||
|
@ -504,6 +535,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
|
|||
if (!package.get())
|
||||
continue;
|
||||
|
||||
onPackageAdded(*package);
|
||||
mPackages.push_back(std::move(package));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#ifndef GAME_MWMECHANICS_AISEQUENCE_H
|
||||
#define GAME_MWMECHANICS_AISEQUENCE_H
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "aistate.hpp"
|
||||
#include "aipackagetypeid.hpp"
|
||||
|
@ -22,8 +23,6 @@ namespace ESM
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class AiPackage;
|
||||
|
@ -33,15 +32,20 @@ namespace MWMechanics
|
|||
struct AiTemporaryBase;
|
||||
typedef DerivedClassStorage<AiTemporaryBase> AiState;
|
||||
|
||||
using AiPackages = std::vector<std::shared_ptr<AiPackage>>;
|
||||
|
||||
/// \brief Sequence of AI-packages for a single actor
|
||||
/** The top-most AI package is run each frame. When completed, it is removed from the stack. **/
|
||||
class AiSequence
|
||||
{
|
||||
///AiPackages to run though
|
||||
std::list<std::shared_ptr<AiPackage>> mPackages;
|
||||
AiPackages mPackages;
|
||||
|
||||
///Finished with top AIPackage, set for one frame
|
||||
bool mDone;
|
||||
bool mDone{};
|
||||
|
||||
int mNumCombatPackages{};
|
||||
int mNumPursuitPackages{};
|
||||
|
||||
///Copy AiSequence
|
||||
void copy (const AiSequence& sequence);
|
||||
|
@ -50,6 +54,11 @@ namespace MWMechanics
|
|||
AiPackageTypeId mLastAiPackage;
|
||||
AiState mAiState;
|
||||
|
||||
void onPackageAdded(const AiPackage& package);
|
||||
void onPackageRemoved(const AiPackage& package);
|
||||
|
||||
AiPackages::iterator erase(AiPackages::iterator package);
|
||||
|
||||
public:
|
||||
///Default constructor
|
||||
AiSequence();
|
||||
|
@ -63,12 +72,31 @@ namespace MWMechanics
|
|||
virtual ~AiSequence();
|
||||
|
||||
/// Iterator may be invalidated by any function calls other than begin() or end().
|
||||
std::list<std::shared_ptr<AiPackage>>::const_iterator begin() const { return mPackages.begin(); }
|
||||
std::list<std::shared_ptr<AiPackage>>::const_iterator end() const { return mPackages.end(); }
|
||||
AiPackages::const_iterator begin() const { return mPackages.begin(); }
|
||||
AiPackages::const_iterator end() const { return mPackages.end(); }
|
||||
|
||||
void erase(std::list<std::shared_ptr<AiPackage>>::const_iterator package);
|
||||
/// Removes all packages controlled by the predicate.
|
||||
template<typename F>
|
||||
void erasePackagesIf(const F&& pred)
|
||||
{
|
||||
mPackages.erase(std::remove_if(mPackages.begin(), mPackages.end(), [&](auto& entry)
|
||||
{
|
||||
const bool doRemove = pred(entry);
|
||||
if (doRemove)
|
||||
onPackageRemoved(*entry);
|
||||
return doRemove;
|
||||
}), mPackages.end());
|
||||
}
|
||||
|
||||
std::list<std::shared_ptr<AiPackage>>& getUnderlyingList() { return mPackages; }
|
||||
/// Removes a single package controlled by the predicate.
|
||||
template<typename F>
|
||||
void erasePackageIf(const F&& pred)
|
||||
{
|
||||
auto it = std::find_if(mPackages.begin(), mPackages.end(), pred);
|
||||
if (it == mPackages.end())
|
||||
return;
|
||||
erase(it);
|
||||
}
|
||||
|
||||
/// Returns currently executing AiPackage type
|
||||
/** \see enum class AiPackageTypeId **/
|
||||
|
@ -89,6 +117,12 @@ namespace MWMechanics
|
|||
/// Is there any combat package?
|
||||
bool isInCombat () const;
|
||||
|
||||
/// Is there any pursuit package.
|
||||
bool isInPursuit() const;
|
||||
|
||||
/// Removes all packages using the specified id.
|
||||
void removePackagesById(AiPackageTypeId id);
|
||||
|
||||
/// Are we in combat with any other actor, who's also engaging us?
|
||||
bool isEngagedWithActor () const;
|
||||
|
||||
|
|
|
@ -55,9 +55,9 @@ namespace
|
|||
|
||||
std::string getBestAttack (const ESM::Weapon* weapon)
|
||||
{
|
||||
int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2;
|
||||
int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2;
|
||||
int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2;
|
||||
int slash = weapon->mData.mSlash[0] + weapon->mData.mSlash[1];
|
||||
int chop = weapon->mData.mChop[0] + weapon->mData.mChop[1];
|
||||
int thrust = weapon->mData.mThrust[0] + weapon->mData.mThrust[1];
|
||||
if (slash == chop && slash == thrust)
|
||||
return "slash";
|
||||
else if (thrust >= chop && thrust >= slash)
|
||||
|
@ -435,6 +435,8 @@ std::string CharacterController::getWeaponAnimation(int weaponType) const
|
|||
else if (isRealWeapon)
|
||||
weaponGroup = oneHandFallback;
|
||||
}
|
||||
else if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr))
|
||||
return "attack1";
|
||||
|
||||
return weaponGroup;
|
||||
}
|
||||
|
@ -708,7 +710,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
refreshHitRecoilAnims(idle);
|
||||
|
||||
std::string weap;
|
||||
if (mPtr.getClass().hasInventoryStore(mPtr))
|
||||
if (mWeaponType != ESM::Weapon::HandToHand || mPtr.getClass().isBipedal(mPtr))
|
||||
weap = getWeaponType(mWeaponType)->mShortGroup;
|
||||
|
||||
refreshJumpAnims(weap, jump, idle, force);
|
||||
|
@ -1119,97 +1121,6 @@ void CharacterController::updateIdleStormState(bool inwater)
|
|||
}
|
||||
}
|
||||
|
||||
bool CharacterController::updateCreatureState()
|
||||
{
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||
|
||||
int weapType = ESM::Weapon::None;
|
||||
if(stats.getDrawState() == DrawState_Weapon)
|
||||
weapType = ESM::Weapon::HandToHand;
|
||||
else if (stats.getDrawState() == DrawState_Spell)
|
||||
weapType = ESM::Weapon::Spell;
|
||||
|
||||
if (weapType != mWeaponType)
|
||||
{
|
||||
mWeaponType = weapType;
|
||||
if (mAnimation->isPlaying(mCurrentWeapon))
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
}
|
||||
|
||||
if(getAttackingOrSpell())
|
||||
{
|
||||
if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None)
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
|
||||
|
||||
std::string startKey = "start";
|
||||
std::string stopKey = "stop";
|
||||
if (weapType == ESM::Weapon::Spell)
|
||||
{
|
||||
const std::string spellid = stats.getSpells().getSelectedSpell();
|
||||
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
|
||||
|
||||
if (!spellid.empty() && canCast)
|
||||
{
|
||||
MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell);
|
||||
cast.playSpellCastingEffects(spellid, false);
|
||||
|
||||
if (!mAnimation->hasAnimation("spellcast"))
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
|
||||
mCastingManualSpell = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(spellid);
|
||||
const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0);
|
||||
|
||||
switch(effectentry.mRange)
|
||||
{
|
||||
case 0: mAttackType = "self"; break;
|
||||
case 1: mAttackType = "touch"; break;
|
||||
case 2: mAttackType = "target"; break;
|
||||
}
|
||||
|
||||
startKey = mAttackType + " " + startKey;
|
||||
stopKey = mAttackType + " " + stopKey;
|
||||
mCurrentWeapon = "spellcast";
|
||||
}
|
||||
}
|
||||
else
|
||||
mCurrentWeapon = "";
|
||||
}
|
||||
|
||||
if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation
|
||||
{
|
||||
mCurrentWeapon = chooseRandomAttackAnimation();
|
||||
}
|
||||
|
||||
if (!mCurrentWeapon.empty())
|
||||
{
|
||||
mAnimation->play(mCurrentWeapon, Priority_Weapon,
|
||||
MWRender::Animation::BlendMask_All, true,
|
||||
1, startKey, stopKey,
|
||||
0.0f, 0);
|
||||
mUpperBodyState = UpperCharState_StartToMinAttack;
|
||||
|
||||
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
|
||||
|
||||
if (weapType == ESM::Weapon::HandToHand)
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
setAttackingOrSpell(false);
|
||||
}
|
||||
|
||||
bool animPlaying = mAnimation->getInfo(mCurrentWeapon);
|
||||
if (!animPlaying)
|
||||
mUpperBodyState = UpperCharState_Nothing;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
|
||||
{
|
||||
// Shields/torches shouldn't be visible during any operation involving two hands
|
||||
|
@ -1218,7 +1129,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
|
|||
return mAnimation->updateCarriedLeftVisible(weaptype);
|
||||
}
|
||||
|
||||
bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||
bool CharacterController::updateState(CharacterState idle)
|
||||
{
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||
|
@ -1386,7 +1297,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
}
|
||||
|
||||
mWeaponType = weaptype;
|
||||
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||
mCurrentWeapon = weapgroup;
|
||||
|
||||
if(!upSoundId.empty() && !isStillWeapon)
|
||||
{
|
||||
|
@ -1456,15 +1367,13 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||
if(getAttackingOrSpell())
|
||||
{
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
|
||||
bool resetIdle = ammunition;
|
||||
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
|
||||
mAttackStrength = 0;
|
||||
|
||||
// Randomize attacks for non-bipedal creatures with Weapon flag
|
||||
// Randomize attacks for non-bipedal creatures
|
||||
if (mPtr.getClass().getType() == ESM::Creature::sRecordId &&
|
||||
!mPtr.getClass().isBipedal(mPtr) &&
|
||||
(!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
|
||||
|
@ -1477,7 +1386,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
// Unset casting flag, otherwise pressing the mouse button down would
|
||||
// continue casting every frame if there is no animation
|
||||
setAttackingOrSpell(false);
|
||||
if (mPtr == player)
|
||||
if (mPtr == getPlayer())
|
||||
{
|
||||
// For the player, set the spell we want to cast
|
||||
// This has to be done at the start of the casting animation,
|
||||
|
@ -1650,7 +1559,14 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
weapSpeed, startKey, stopKey,
|
||||
0.0f, 0);
|
||||
if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_StartToMinAttack;
|
||||
if (isRandomAttackAnimation(mCurrentWeapon))
|
||||
{
|
||||
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2346,11 +2262,7 @@ void CharacterController::update(float duration)
|
|||
|
||||
if (!mSkipAnim)
|
||||
{
|
||||
// bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
|
||||
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
|
||||
forcestateupdate = updateWeaponState(idlestate) || forcestateupdate;
|
||||
else
|
||||
forcestateupdate = updateCreatureState() || forcestateupdate;
|
||||
forcestateupdate = updateState(idlestate) || forcestateupdate;
|
||||
|
||||
refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
|
||||
updateIdleStormState(inwater);
|
||||
|
@ -2879,10 +2791,7 @@ bool CharacterController::readyToStartAttack() const
|
|||
if (mHitState != CharState_None && mHitState != CharState_Block)
|
||||
return false;
|
||||
|
||||
if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))
|
||||
return mUpperBodyState == UpperCharState_WeapEquiped;
|
||||
else
|
||||
return mUpperBodyState == UpperCharState_Nothing;
|
||||
return mUpperBodyState == UpperCharState_WeapEquiped;
|
||||
}
|
||||
|
||||
float CharacterController::getAttackStrength() const
|
||||
|
|
|
@ -205,8 +205,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
|||
|
||||
void clearAnimQueue(bool clearPersistAnims = false);
|
||||
|
||||
bool updateWeaponState(CharacterState& idle);
|
||||
bool updateCreatureState();
|
||||
bool updateState(CharacterState idle);
|
||||
void updateIdleStormState(bool inwater);
|
||||
|
||||
std::string chooseRandomAttackAnimation() const;
|
||||
|
|
|
@ -26,8 +26,6 @@ namespace MWMechanics
|
|||
mDeathAnimation(-1), mTimeOfDeath(), mSideMovementAngle(0), mLevel (0)
|
||||
, mAttackingOrSpell(false)
|
||||
{
|
||||
for (int i=0; i<4; ++i)
|
||||
mAiSettings[i] = 0;
|
||||
}
|
||||
|
||||
const AiSequence& CreatureStats::getAiSequence() const
|
||||
|
@ -158,9 +156,8 @@ namespace MWMechanics
|
|||
float agility = getAttribute(ESM::Attribute::Agility).getModified();
|
||||
float endurance = getAttribute(ESM::Attribute::Endurance).getModified();
|
||||
DynamicStat<float> fatigue = getFatigue();
|
||||
float diff = (strength+willpower+agility+endurance) - fatigue.getBase();
|
||||
float currentToBaseRatio = fatigue.getBase() > 0 ? (fatigue.getCurrent() / fatigue.getBase()) : 0;
|
||||
fatigue.setModified(fatigue.getModified() + diff, 0);
|
||||
fatigue.setBase(std::max(0.f, strength + willpower + agility + endurance));
|
||||
fatigue.setCurrent(fatigue.getBase() * currentToBaseRatio, false, true);
|
||||
setFatigue(fatigue);
|
||||
}
|
||||
|
@ -196,8 +193,6 @@ namespace MWMechanics
|
|||
|
||||
mDead = true;
|
||||
|
||||
mDynamic[index].setModifier(0);
|
||||
mDynamic[index].setCurrentModifier(0);
|
||||
mDynamic[index].setCurrent(0);
|
||||
}
|
||||
}
|
||||
|
@ -281,10 +276,7 @@ namespace MWMechanics
|
|||
{
|
||||
if (mDead)
|
||||
{
|
||||
if (mDynamic[0].getModified() < 1)
|
||||
mDynamic[0].setModified(1, 0);
|
||||
|
||||
mDynamic[0].setCurrent(mDynamic[0].getModified());
|
||||
mDynamic[0].setCurrent(mDynamic[0].getBase());
|
||||
mDead = false;
|
||||
mDeathAnimationFinished = false;
|
||||
}
|
||||
|
@ -415,9 +407,8 @@ namespace MWMechanics
|
|||
double magickaFactor = base + mMagicEffects.get(EffectKey(ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1;
|
||||
|
||||
DynamicStat<float> magicka = getMagicka();
|
||||
float diff = (static_cast<int>(magickaFactor*intelligence)) - magicka.getBase();
|
||||
float currentToBaseRatio = magicka.getBase() > 0 ? magicka.getCurrent() / magicka.getBase() : 0;
|
||||
magicka.setModified(magicka.getModified() + diff, 0);
|
||||
magicka.setBase(magickaFactor * intelligence);
|
||||
magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true);
|
||||
setMagicka(magicka);
|
||||
}
|
||||
|
|
|
@ -1318,7 +1318,7 @@ namespace MWMechanics
|
|||
// once the bounty has been paid.
|
||||
actor.getClass().getNpcStats(actor).setCrimeId(id);
|
||||
|
||||
if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
|
||||
if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit())
|
||||
{
|
||||
actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor);
|
||||
}
|
||||
|
@ -1396,7 +1396,7 @@ namespace MWMechanics
|
|||
{
|
||||
// Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
|
||||
// Note: accidental or collateral damage attacks are ignored.
|
||||
if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
|
||||
if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit())
|
||||
startCombat(victim, player);
|
||||
|
||||
// Set the crime ID, which we will use to calm down participants
|
||||
|
@ -1442,7 +1442,7 @@ namespace MWMechanics
|
|||
{
|
||||
// Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
|
||||
// Note: accidental or collateral damage attacks are ignored.
|
||||
if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
|
||||
if (!target.getClass().getCreatureStats(target).getAiSequence().isInPursuit())
|
||||
{
|
||||
// If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player,
|
||||
// he will attack the player only if we will force him (e.g. via StartCombat console command)
|
||||
|
@ -1450,7 +1450,7 @@ namespace MWMechanics
|
|||
std::string script = target.getClass().getScript(target);
|
||||
if (!script.empty() && target.getRefData().getLocals().hasVar(script, "onpchitme") && attacker == player)
|
||||
{
|
||||
int fight = std::max(0, target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified());
|
||||
int fight = target.getClass().getCreatureStats(target).getAiSetting(CreatureStats::AI_Fight).getModified();
|
||||
peaceful = (fight == 0);
|
||||
}
|
||||
|
||||
|
@ -1467,7 +1467,7 @@ namespace MWMechanics
|
|||
const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence();
|
||||
return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker)
|
||||
&& !isAggressive(target, attacker) && !seq.isEngagedWithActor()
|
||||
&& !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue);
|
||||
&& !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit();
|
||||
}
|
||||
|
||||
void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker)
|
||||
|
|
|
@ -64,8 +64,7 @@ namespace
|
|||
auto& creatureStats = target.getClass().getCreatureStats(target);
|
||||
auto stat = creatureStats.getDynamic(index);
|
||||
float current = stat.getCurrent();
|
||||
stat.setModified(stat.getModified() + magnitude, 0);
|
||||
stat.setCurrentModified(stat.getCurrentModified() + magnitude);
|
||||
stat.setBase(std::max(0.f, stat.getBase() + magnitude));
|
||||
stat.setCurrent(current + magnitude);
|
||||
creatureStats.setDynamic(index, stat);
|
||||
}
|
||||
|
@ -980,12 +979,10 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara
|
|||
if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
|
||||
{
|
||||
auto& seq = target.getClass().getCreatureStats(target).getAiSequence();
|
||||
auto it = std::find_if(seq.begin(), seq.end(), [&](const auto& package)
|
||||
seq.erasePackageIf([&](const auto& package)
|
||||
{
|
||||
return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast<const MWMechanics::AiFollow*>(package.get())->isCommanded();
|
||||
});
|
||||
if(it != seq.end())
|
||||
seq.erase(it);
|
||||
}
|
||||
break;
|
||||
case ESM::MagicEffect::ExtraSpell:
|
||||
|
|
|
@ -5,174 +5,41 @@
|
|||
namespace MWMechanics
|
||||
{
|
||||
template<typename T>
|
||||
Stat<T>::Stat() : mBase (0), mModified (0), mCurrentModified (0) {}
|
||||
Stat<T>::Stat() : mBase (0), mModifier (0) {}
|
||||
template<typename T>
|
||||
Stat<T>::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {}
|
||||
template<typename T>
|
||||
Stat<T>::Stat(T base, T modified) : mBase (base), mModified (modified), mCurrentModified (modified) {}
|
||||
|
||||
template<typename T>
|
||||
const T& Stat<T>::getBase() const
|
||||
{
|
||||
return mBase;
|
||||
}
|
||||
Stat<T>::Stat(T base, T modified) : mBase (base), mModifier (modified) {}
|
||||
|
||||
template<typename T>
|
||||
T Stat<T>::getModified(bool capped) const
|
||||
{
|
||||
if(!capped)
|
||||
return mModified;
|
||||
return std::max(static_cast<T>(0), mModified);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T Stat<T>::getCurrentModified() const
|
||||
{
|
||||
return mCurrentModified;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T Stat<T>::getModifier() const
|
||||
{
|
||||
return mModified-mBase;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T Stat<T>::getCurrentModifier() const
|
||||
{
|
||||
return mCurrentModified - mModified;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Stat<T>::set (const T& value)
|
||||
{
|
||||
T diff = value - mBase;
|
||||
mBase = mModified = value;
|
||||
mCurrentModified += diff;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Stat<T>::setBase (const T& value)
|
||||
{
|
||||
T diff = value - mBase;
|
||||
mBase = value;
|
||||
mModified += diff;
|
||||
mCurrentModified += diff;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Stat<T>::setModified (T value, const T& min, const T& max)
|
||||
{
|
||||
T diff = value - mModified;
|
||||
|
||||
if (mBase+diff<min)
|
||||
{
|
||||
value = min + (mModified - mBase);
|
||||
diff = value - mModified;
|
||||
}
|
||||
else if (mBase+diff>max)
|
||||
{
|
||||
value = max + (mModified - mBase);
|
||||
diff = value - mModified;
|
||||
}
|
||||
|
||||
mModified = value;
|
||||
mBase += diff;
|
||||
mCurrentModified += diff;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Stat<T>::setCurrentModified(T value)
|
||||
{
|
||||
mCurrentModified = value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Stat<T>::setModifier (const T& modifier)
|
||||
{
|
||||
mModified = mBase + modifier;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Stat<T>::setCurrentModifier(const T& modifier)
|
||||
{
|
||||
mCurrentModified = mModified + modifier;
|
||||
if(capped)
|
||||
return std::max({}, mModifier + mBase);
|
||||
return mModifier + mBase;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Stat<T>::writeState (ESM::StatState<T>& state) const
|
||||
{
|
||||
state.mBase = mBase;
|
||||
state.mMod = mCurrentModified;
|
||||
state.mMod = mModifier;
|
||||
}
|
||||
template<typename T>
|
||||
void Stat<T>::readState (const ESM::StatState<T>& state)
|
||||
{
|
||||
mBase = state.mBase;
|
||||
mModified = state.mBase;
|
||||
mCurrentModified = state.mMod;
|
||||
mModifier = state.mMod;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
DynamicStat<T>::DynamicStat() : mStatic (0), mCurrent (0) {}
|
||||
DynamicStat<T>::DynamicStat() : mStatic(0, 0), mCurrent(0) {}
|
||||
template<typename T>
|
||||
DynamicStat<T>::DynamicStat(T base) : mStatic (base), mCurrent (base) {}
|
||||
DynamicStat<T>::DynamicStat(T base) : mStatic(base, 0), mCurrent(base) {}
|
||||
template<typename T>
|
||||
DynamicStat<T>::DynamicStat(T base, T modified, T current) : mStatic(base, modified), mCurrent (current) {}
|
||||
template<typename T>
|
||||
DynamicStat<T>::DynamicStat(const Stat<T> &stat, T current) : mStatic(stat), mCurrent (current) {}
|
||||
|
||||
|
||||
template<typename T>
|
||||
const T& DynamicStat<T>::getBase() const
|
||||
{
|
||||
return mStatic.getBase();
|
||||
}
|
||||
template<typename T>
|
||||
T DynamicStat<T>::getModified() const
|
||||
{
|
||||
return mStatic.getModified();
|
||||
}
|
||||
template<typename T>
|
||||
T DynamicStat<T>::getCurrentModified() const
|
||||
{
|
||||
return mStatic.getCurrentModified();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T& DynamicStat<T>::getCurrent() const
|
||||
{
|
||||
return mCurrent;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void DynamicStat<T>::set (const T& value)
|
||||
{
|
||||
mStatic.set (value);
|
||||
mCurrent = value;
|
||||
}
|
||||
template<typename T>
|
||||
void DynamicStat<T>::setBase (const T& value)
|
||||
{
|
||||
mStatic.setBase (value);
|
||||
|
||||
if (mCurrent>getModified())
|
||||
mCurrent = getModified();
|
||||
}
|
||||
template<typename T>
|
||||
void DynamicStat<T>::setModified (T value, const T& min, const T& max)
|
||||
{
|
||||
mStatic.setModified (value, min, max);
|
||||
|
||||
if (mCurrent>getModified())
|
||||
mCurrent = getModified();
|
||||
}
|
||||
template<typename T>
|
||||
void DynamicStat<T>::setCurrentModified(T value)
|
||||
{
|
||||
mStatic.setCurrentModified(value);
|
||||
}
|
||||
template<typename T>
|
||||
void DynamicStat<T>::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified)
|
||||
{
|
||||
|
@ -197,22 +64,18 @@ namespace MWMechanics
|
|||
mCurrent = 0;
|
||||
}
|
||||
}
|
||||
template<typename T>
|
||||
void DynamicStat<T>::setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero)
|
||||
{
|
||||
T diff = modifier - mStatic.getModifier();
|
||||
mStatic.setModifier (modifier);
|
||||
setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void DynamicStat<T>::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero)
|
||||
T DynamicStat<T>::getRatio(bool nanIsZero) const
|
||||
{
|
||||
T diff = modifier - mStatic.getCurrentModifier();
|
||||
mStatic.setCurrentModifier(modifier);
|
||||
|
||||
// The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active).
|
||||
setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0));
|
||||
T modified = getModified();
|
||||
if(modified == T{})
|
||||
{
|
||||
if(nanIsZero)
|
||||
return modified;
|
||||
return {1};
|
||||
}
|
||||
return getCurrent() / modified;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -16,38 +16,22 @@ namespace MWMechanics
|
|||
class Stat
|
||||
{
|
||||
T mBase;
|
||||
T mModified;
|
||||
T mCurrentModified;
|
||||
T mModifier;
|
||||
|
||||
public:
|
||||
typedef T Type;
|
||||
|
||||
Stat();
|
||||
Stat(T base);
|
||||
Stat(T base, T modified);
|
||||
|
||||
const T& getBase() const;
|
||||
const T& getBase() const { return mBase; };
|
||||
|
||||
T getModified(bool capped = true) const;
|
||||
T getCurrentModified() const;
|
||||
T getModifier() const;
|
||||
T getCurrentModifier() const;
|
||||
T getModifier() const { return mModifier; };
|
||||
|
||||
/// Set base and modified to \a value.
|
||||
void set (const T& value);
|
||||
void setBase(const T& value) { mBase = value; };
|
||||
|
||||
/// Set base and adjust modified accordingly.
|
||||
void setBase (const T& value);
|
||||
|
||||
/// Set modified value and adjust base accordingly.
|
||||
void setModified (T value, const T& min, const T& max = std::numeric_limits<T>::max());
|
||||
|
||||
/// Set "current modified," used for drain and fortify. Unlike the regular modifier
|
||||
/// this just adds and subtracts from the current value without changing the maximum.
|
||||
void setCurrentModified(T value);
|
||||
|
||||
void setModifier (const T& modifier);
|
||||
void setCurrentModifier (const T& modifier);
|
||||
void setModifier(const T& modifier) { mModifier = modifier; };
|
||||
|
||||
void writeState (ESM::StatState<T>& state) const;
|
||||
void readState (const ESM::StatState<T>& state);
|
||||
|
@ -57,7 +41,7 @@ namespace MWMechanics
|
|||
inline bool operator== (const Stat<T>& left, const Stat<T>& right)
|
||||
{
|
||||
return left.getBase()==right.getBase() &&
|
||||
left.getModified()==right.getModified();
|
||||
left.getModifier()==right.getModifier();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
@ -80,27 +64,18 @@ namespace MWMechanics
|
|||
DynamicStat(T base, T modified, T current);
|
||||
DynamicStat(const Stat<T> &stat, T current);
|
||||
|
||||
const T& getBase() const;
|
||||
T getModified() const;
|
||||
T getCurrentModified() const;
|
||||
const T& getCurrent() const;
|
||||
const T& getBase() const { return mStatic.getBase(); };
|
||||
T getModified(bool capped = true) const { return mStatic.getModified(capped); };
|
||||
const T& getCurrent() const { return mCurrent; };
|
||||
T getRatio(bool nanIsZero = true) const;
|
||||
|
||||
/// Set base, modified and current to \a value.
|
||||
void set (const T& value);
|
||||
|
||||
/// Set base and adjust modified accordingly.
|
||||
void setBase (const T& value);
|
||||
|
||||
/// Set modified value and adjust base accordingly.
|
||||
void setModified (T value, const T& min, const T& max = std::numeric_limits<T>::max());
|
||||
|
||||
/// Set "current modified," used for drain and fortify. Unlike the regular modifier
|
||||
/// this just adds and subtracts from the current value without changing the maximum.
|
||||
void setCurrentModified(T value);
|
||||
/// Set base and adjust current accordingly.
|
||||
void setBase(const T& value) { mStatic.setBase(value); };
|
||||
|
||||
void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false);
|
||||
void setModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero=false);
|
||||
void setCurrentModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero = false);
|
||||
|
||||
T getModifier() const { return mStatic.getModifier(); }
|
||||
void setModifier(T value) { mStatic.setModifier(value); }
|
||||
|
||||
void writeState (ESM::StatState<T>& state) const;
|
||||
void readState (const ESM::StatState<T>& state);
|
||||
|
@ -110,7 +85,7 @@ namespace MWMechanics
|
|||
inline bool operator== (const DynamicStat<T>& left, const DynamicStat<T>& right)
|
||||
{
|
||||
return left.getBase()==right.getBase() &&
|
||||
left.getModified()==right.getModified() &&
|
||||
left.getModifier()==right.getModifier() &&
|
||||
left.getCurrent()==right.getCurrent();
|
||||
}
|
||||
|
||||
|
|
|
@ -93,6 +93,26 @@ namespace
|
|||
MWRender::GlobalMap* mParent;
|
||||
};
|
||||
|
||||
std::vector<char> writePng(const osg::Image& overlayImage)
|
||||
{
|
||||
std::ostringstream ostream;
|
||||
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png");
|
||||
if (!readerwriter)
|
||||
{
|
||||
Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found";
|
||||
return std::vector<char>();
|
||||
}
|
||||
|
||||
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(overlayImage, ostream);
|
||||
if (!result.success())
|
||||
{
|
||||
Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status();
|
||||
return std::vector<char>();
|
||||
}
|
||||
|
||||
std::string data = ostream.str();
|
||||
return std::vector<char>(data.begin(), data.end());
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
|
@ -221,6 +241,20 @@ namespace MWRender
|
|||
osg::ref_ptr<osg::Texture2D> mOverlayTexture;
|
||||
};
|
||||
|
||||
struct GlobalMap::WritePng final : public SceneUtil::WorkItem
|
||||
{
|
||||
osg::ref_ptr<const osg::Image> mOverlayImage;
|
||||
std::vector<char> mImageData;
|
||||
|
||||
explicit WritePng(osg::ref_ptr<const osg::Image> overlayImage)
|
||||
: mOverlayImage(std::move(overlayImage)) {}
|
||||
|
||||
void doWork() override
|
||||
{
|
||||
mImageData = writePng(*mOverlayImage);
|
||||
}
|
||||
};
|
||||
|
||||
GlobalMap::GlobalMap(osg::Group* root, SceneUtil::WorkQueue* workQueue)
|
||||
: mRoot(root)
|
||||
, mWorkQueue(workQueue)
|
||||
|
@ -400,23 +434,15 @@ namespace MWRender
|
|||
map.mBounds.mMinY = mMinY;
|
||||
map.mBounds.mMaxY = mMaxY;
|
||||
|
||||
std::ostringstream ostream;
|
||||
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png");
|
||||
if (!readerwriter)
|
||||
if (mWritePng != nullptr)
|
||||
{
|
||||
Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found";
|
||||
mWritePng->waitTillDone();
|
||||
map.mImageData = std::move(mWritePng->mImageData);
|
||||
mWritePng = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream);
|
||||
if (!result.success())
|
||||
{
|
||||
Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string data = ostream.str();
|
||||
map.mImageData = std::vector<char>(data.begin(), data.end());
|
||||
map.mImageData = writePng(*mOverlayImage);
|
||||
}
|
||||
|
||||
struct Box
|
||||
|
@ -606,4 +632,11 @@ namespace MWRender
|
|||
cam->removeChildren(0, cam->getNumChildren());
|
||||
mRoot->removeChild(cam);
|
||||
}
|
||||
|
||||
void GlobalMap::asyncWritePng()
|
||||
{
|
||||
// Use deep copy to avoid any sychronization
|
||||
mWritePng = new WritePng(new osg::Image(*mOverlayImage, osg::CopyOp::DEEP_COPY_ALL));
|
||||
mWorkQueue->addWorkItem(mWritePng, /*front=*/true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,11 @@ namespace MWRender
|
|||
|
||||
void ensureLoaded();
|
||||
|
||||
void asyncWritePng();
|
||||
|
||||
private:
|
||||
struct WritePng;
|
||||
|
||||
/**
|
||||
* Request rendering a 2d quad onto mOverlayTexture.
|
||||
* x, y, width and height are the destination coordinates (top-left coordinate origin)
|
||||
|
@ -121,6 +125,7 @@ namespace MWRender
|
|||
|
||||
osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;
|
||||
osg::ref_ptr<CreateMapWorkItem> mWorkItem;
|
||||
osg::ref_ptr<WritePng> mWritePng;
|
||||
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
|
|
|
@ -296,7 +296,7 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
|||
return;
|
||||
|
||||
mViewMode = viewMode;
|
||||
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change
|
||||
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change
|
||||
|
||||
mAmmunition.reset();
|
||||
rebuild();
|
||||
|
|
|
@ -218,8 +218,8 @@ namespace MWScript
|
|||
MWMechanics::DynamicStat<float> stat (ptr.getClass().getCreatureStats (ptr)
|
||||
.getDynamic (mIndex));
|
||||
|
||||
stat.setModified (value, 0);
|
||||
stat.setCurrent(value);
|
||||
stat.setBase(value);
|
||||
stat.setCurrent(stat.getModified(false), true, true);
|
||||
|
||||
ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat);
|
||||
}
|
||||
|
@ -259,19 +259,18 @@ namespace MWScript
|
|||
}
|
||||
}
|
||||
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
|
||||
Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent();
|
||||
MWMechanics::DynamicStat<float> stat = stats.getDynamic(mIndex);
|
||||
|
||||
MWMechanics::DynamicStat<float> stat (ptr.getClass().getCreatureStats (ptr)
|
||||
.getDynamic (mIndex));
|
||||
float current = stat.getCurrent();
|
||||
float base = diff + stat.getBase();
|
||||
if(mIndex != 2)
|
||||
base = std::max(base, 0.f);
|
||||
stat.setBase(base);
|
||||
stat.setCurrent(diff + current, true, true);
|
||||
|
||||
stat.setModified (diff + stat.getModified(), 0);
|
||||
stat.setCurrentModified (diff + stat.getCurrentModified());
|
||||
|
||||
stat.setCurrent (diff + current);
|
||||
|
||||
ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat);
|
||||
stats.setDynamic (mIndex, stat);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -325,17 +324,9 @@ namespace MWScript
|
|||
void execute (Interpreter::Runtime& runtime) override
|
||||
{
|
||||
MWWorld::Ptr ptr = R()(runtime);
|
||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr);
|
||||
|
||||
Interpreter::Type_Float value = 0;
|
||||
|
||||
Interpreter::Type_Float max = stats.getDynamic(mIndex).getModified();
|
||||
|
||||
if (max>0)
|
||||
value = stats.getDynamic(mIndex).getCurrent() / max;
|
||||
|
||||
runtime.push (value);
|
||||
runtime.push(stats.getDynamic(mIndex).getRatio());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -188,6 +188,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
|
|||
|
||||
try
|
||||
{
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap();
|
||||
|
||||
if (!character)
|
||||
{
|
||||
MWWorld::ConstPtr player = MWMechanics::getPlayer();
|
||||
|
@ -255,9 +259,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
|
|||
+MWBase::Environment::get().getWorld()->countSavedGameRecords()
|
||||
+MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords()
|
||||
+MWBase::Environment::get().getDialogueManager()->countSavedGameRecords()
|
||||
+MWBase::Environment::get().getWindowManager()->countSavedGameRecords()
|
||||
+MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords()
|
||||
+MWBase::Environment::get().getInputManager()->countSavedGameRecords();
|
||||
+MWBase::Environment::get().getInputManager()->countSavedGameRecords()
|
||||
+MWBase::Environment::get().getWindowManager()->countSavedGameRecords();
|
||||
writer.setRecordCount (recordCount);
|
||||
|
||||
writer.save (stream);
|
||||
|
@ -280,9 +284,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
|
|||
MWBase::Environment::get().getLuaManager()->write(writer, listener);
|
||||
MWBase::Environment::get().getWorld()->write (writer, listener);
|
||||
MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener);
|
||||
MWBase::Environment::get().getWindowManager()->write(writer, listener);
|
||||
MWBase::Environment::get().getMechanicsManager()->write(writer, listener);
|
||||
MWBase::Environment::get().getInputManager()->write(writer, listener);
|
||||
MWBase::Environment::get().getWindowManager()->write(writer, listener);
|
||||
|
||||
// Ensure we have written the number of records that was estimated
|
||||
if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record
|
||||
|
@ -302,6 +306,11 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
|
|||
|
||||
Settings::Manager::setString ("character", "Saves",
|
||||
slot->mPath.parent_path().filename().string());
|
||||
|
||||
const auto finish = std::chrono::steady_clock::now();
|
||||
|
||||
Log(Debug::Info) << '\'' << description << "' is saved in "
|
||||
<< std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(finish - start).count() << "ms";
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
|
|
@ -185,6 +185,11 @@ namespace
|
|||
else if constexpr (std::is_same_v<T, ESM::NPC>)
|
||||
MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats);
|
||||
}
|
||||
else if(state.mVersion < 20)
|
||||
{
|
||||
if constexpr (std::is_same_v<T, ESM::Creature> || std::is_same_v<T, ESM::NPC>)
|
||||
MWWorld::convertStats(state.mCreatureStats);
|
||||
}
|
||||
|
||||
if (state.mRef.mRefNum.hasContentFile())
|
||||
{
|
||||
|
|
|
@ -198,7 +198,11 @@ namespace MWWorld
|
|||
for(std::size_t i = 0; i < ESM::Attribute::Length; ++i)
|
||||
creatureStats.mAttributes[i].mMod = 0.f;
|
||||
for(std::size_t i = 0; i < 3; ++i)
|
||||
creatureStats.mDynamic[i].mMod = 0.f;
|
||||
{
|
||||
auto& dynamic = creatureStats.mDynamic[i];
|
||||
dynamic.mCurrent -= dynamic.mMod - dynamic.mBase;
|
||||
dynamic.mMod = 0.f;
|
||||
}
|
||||
for(std::size_t i = 0; i < 4; ++i)
|
||||
creatureStats.mAiSettings[i].mMod = 0.f;
|
||||
if(npcStats)
|
||||
|
@ -207,4 +211,13 @@ namespace MWWorld
|
|||
npcStats->mSkills[i].mMod = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
// Versions 17-19 wrote different modifiers to the savegame depending on whether the save had upgraded from a pre-17 version or not
|
||||
void convertStats(ESM::CreatureStats& creatureStats)
|
||||
{
|
||||
for(std::size_t i = 0; i < 3; ++i)
|
||||
creatureStats.mDynamic[i].mMod = 0.f;
|
||||
for(std::size_t i = 0; i < 4; ++i)
|
||||
creatureStats.mAiSettings[i].mMod = 0.f;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace MWWorld
|
|||
{
|
||||
void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory,
|
||||
ESM::NpcStats* npcStats = nullptr);
|
||||
|
||||
void convertStats(ESM::CreatureStats& creatureStats);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -383,6 +383,8 @@ namespace MWWorld
|
|||
}
|
||||
if (reader.getFormat() < 17)
|
||||
convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats);
|
||||
else if(reader.getFormat() < 20)
|
||||
convertStats(player.mObject.mCreatureStats);
|
||||
|
||||
if (!player.mObject.mEnabled)
|
||||
{
|
||||
|
|
|
@ -1281,9 +1281,9 @@ namespace MWWorld
|
|||
return moveObject(ptr, newpos);
|
||||
}
|
||||
|
||||
void World::scaleObject (const Ptr& ptr, float scale)
|
||||
void World::scaleObject (const Ptr& ptr, float scale, bool force)
|
||||
{
|
||||
if (scale == ptr.getCellRef().getScale())
|
||||
if (!force && scale == ptr.getCellRef().getScale())
|
||||
return;
|
||||
if (mPhysics->getActor(ptr))
|
||||
mNavigator->removeAgent(getPathfindingHalfExtents(ptr));
|
||||
|
@ -2482,7 +2482,7 @@ namespace MWWorld
|
|||
player.getClass().getInventoryStore(player).setInvListener(anim, player);
|
||||
player.getClass().getInventoryStore(player).setContListener(anim);
|
||||
|
||||
scaleObject(player, player.getCellRef().getScale()); // apply race height
|
||||
scaleObject(player, player.getCellRef().getScale(), true); // apply race height
|
||||
rotateObject(player, osg::Vec3f(), MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust);
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr());
|
||||
|
|
|
@ -377,7 +377,7 @@ namespace MWWorld
|
|||
MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec) override;
|
||||
///< @return an updated Ptr
|
||||
|
||||
void scaleObject (const Ptr& ptr, float scale) override;
|
||||
void scaleObject (const Ptr& ptr, float scale, bool force = false) override;
|
||||
|
||||
/// World rotates object, uses radians
|
||||
/// @note Rotations via this method use a different rotation order than the initial rotations in the CS. This
|
||||
|
|
|
@ -70,6 +70,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
esmloader/esmdata.cpp
|
||||
|
||||
files/hash.cpp
|
||||
|
||||
toutf8/toutf8.cpp
|
||||
)
|
||||
|
||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
@ -93,6 +95,8 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
EXPECTED_MD5 bf3691034a38611534c74c3b89a7d2c3
|
||||
)
|
||||
|
||||
target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data")
|
||||
target_compile_definitions(openmw_test_suite
|
||||
PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data"
|
||||
OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
endif()
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include "components/esm/esmcommon.hpp"
|
||||
#include "components/esm/defs.hpp"
|
||||
|
||||
TEST(EsmFixedString, operator__eq_ne)
|
||||
{
|
||||
{
|
||||
SCOPED_TRACE("asdc == asdc");
|
||||
ESM::NAME name;
|
||||
name.assign("asdc");
|
||||
constexpr ESM::NAME name("asdc");
|
||||
char s[4] = {'a', 's', 'd', 'c'};
|
||||
std::string ss(s, 4);
|
||||
|
||||
|
@ -16,8 +16,7 @@ TEST(EsmFixedString, operator__eq_ne)
|
|||
}
|
||||
{
|
||||
SCOPED_TRACE("asdc == asdcx");
|
||||
ESM::NAME name;
|
||||
name.assign("asdc");
|
||||
constexpr ESM::NAME name("asdc");
|
||||
char s[5] = {'a', 's', 'd', 'c', 'x'};
|
||||
std::string ss(s, 5);
|
||||
|
||||
|
@ -27,8 +26,7 @@ TEST(EsmFixedString, operator__eq_ne)
|
|||
}
|
||||
{
|
||||
SCOPED_TRACE("asdc == asdc[NULL]");
|
||||
ESM::NAME name;
|
||||
name.assign("asdc");
|
||||
const ESM::NAME name("asdc");
|
||||
char s[5] = {'a', 's', 'd', 'c', '\0'};
|
||||
std::string ss(s, 5);
|
||||
|
||||
|
@ -41,8 +39,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
|
|||
{
|
||||
{
|
||||
SCOPED_TRACE("asdc == asdc (const)");
|
||||
ESM::NAME name;
|
||||
name.assign("asdc");
|
||||
constexpr ESM::NAME name("asdc");
|
||||
const char s[4] = { 'a', 's', 'd', 'c' };
|
||||
std::string ss(s, 4);
|
||||
|
||||
|
@ -52,8 +49,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
|
|||
}
|
||||
{
|
||||
SCOPED_TRACE("asdc == asdcx (const)");
|
||||
ESM::NAME name;
|
||||
name.assign("asdc");
|
||||
constexpr ESM::NAME name("asdc");
|
||||
const char s[5] = { 'a', 's', 'd', 'c', 'x' };
|
||||
std::string ss(s, 5);
|
||||
|
||||
|
@ -63,8 +59,7 @@ TEST(EsmFixedString, operator__eq_ne_const)
|
|||
}
|
||||
{
|
||||
SCOPED_TRACE("asdc == asdc[NULL] (const)");
|
||||
ESM::NAME name;
|
||||
name.assign("asdc");
|
||||
constexpr ESM::NAME name("asdc");
|
||||
const char s[5] = { 'a', 's', 'd', 'c', '\0' };
|
||||
std::string ss(s, 5);
|
||||
|
||||
|
@ -148,3 +143,15 @@ TEST(EsmFixedString, assignment_operator_is_supported_for_uint32)
|
|||
value = static_cast<uint32_t>(0xFEDCBA98u);
|
||||
EXPECT_EQ(value, static_cast<uint32_t>(0xFEDCBA98u)) << value.toInt();
|
||||
}
|
||||
|
||||
TEST(EsmFixedString, construction_from_uint32_is_supported)
|
||||
{
|
||||
constexpr ESM::NAME value(0xFEDCBA98u);
|
||||
EXPECT_EQ(value, static_cast<std::uint32_t>(0xFEDCBA98u)) << value.toInt();
|
||||
}
|
||||
|
||||
TEST(EsmFixedString, construction_from_RecNameInts_is_supported)
|
||||
{
|
||||
constexpr ESM::NAME value(ESM::RecNameInts::REC_ACTI);
|
||||
EXPECT_EQ(value, static_cast<std::uint32_t>(ESM::RecNameInts::REC_ACTI)) << value.toInt();
|
||||
}
|
||||
|
|
|
@ -39,6 +39,10 @@ namespace
|
|||
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector2(3/5, 4/5)"));
|
||||
lua.safe_script("_, len = util.vector2(0, 0):normalize()");
|
||||
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
|
||||
lua.safe_script("ediv0 = util.vector2(1, 0):ediv(util.vector2(0, 0))");
|
||||
EXPECT_TRUE(get<bool>(lua, "ediv0.x == math.huge and ediv0.y ~= ediv0.y"));
|
||||
EXPECT_TRUE(get<bool>(lua, "util.vector2(1, 2):emul(util.vector2(3, 4)) == util.vector2(3, 8)"));
|
||||
EXPECT_TRUE(get<bool>(lua, "util.vector2(4, 6):ediv(util.vector2(2, 3)) == util.vector2(2, 2)"));
|
||||
}
|
||||
|
||||
TEST(LuaUtilPackageTest, Vector3)
|
||||
|
@ -68,6 +72,10 @@ namespace
|
|||
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector3(3/5, 4/5, 0)"));
|
||||
lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()");
|
||||
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
|
||||
lua.safe_script("ediv0 = util.vector3(1, 1, 1):ediv(util.vector3(0, 0, 0))");
|
||||
EXPECT_TRUE(get<bool>(lua, "ediv0.z == math.huge"));
|
||||
EXPECT_TRUE(get<bool>(lua, "util.vector3(1, 2, 3):emul(util.vector3(3, 4, 5)) == util.vector3(3, 8, 15)"));
|
||||
EXPECT_TRUE(get<bool>(lua, "util.vector3(4, 6, 8):ediv(util.vector3(2, 3, 4)) == util.vector3(2, 2, 2)"));
|
||||
}
|
||||
|
||||
TEST(LuaUtilPackageTest, Vector4)
|
||||
|
@ -95,6 +103,10 @@ namespace
|
|||
EXPECT_TRUE(get<bool>(lua, "v2 == util.vector4(3/5, 0, 0, 4/5)"));
|
||||
lua.safe_script("_, len = util.vector4(0, 0, 0, 0):normalize()");
|
||||
EXPECT_FLOAT_EQ(get<float>(lua, "len"), 0);
|
||||
lua.safe_script("ediv0 = util.vector4(1, 1, 1, -1):ediv(util.vector4(0, 0, 0, 0))");
|
||||
EXPECT_TRUE(get<bool>(lua, "ediv0.w == -math.huge"));
|
||||
EXPECT_TRUE(get<bool>(lua, "util.vector4(1, 2, 3, 4):emul(util.vector4(3, 4, 5, 6)) == util.vector4(3, 8, 15, 24)"));
|
||||
EXPECT_TRUE(get<bool>(lua, "util.vector4(4, 6, 8, 9):ediv(util.vector4(2, 3, 4, 3)) == util.vector4(2, 2, 2, 3)"));
|
||||
}
|
||||
|
||||
TEST(LuaUtilPackageTest, Color)
|
||||
|
|
159
apps/openmw_test_suite/toutf8/toutf8.cpp
Normal file
159
apps/openmw_test_suite/toutf8/toutf8.cpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
#include <components/to_utf8/to_utf8.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#ifndef OPENMW_TEST_SUITE_SOURCE_DIR
|
||||
#define OPENMW_TEST_SUITE_SOURCE_DIR ""
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace ToUTF8;
|
||||
|
||||
struct Params
|
||||
{
|
||||
FromType mLegacyEncoding;
|
||||
std::string mLegacyEncodingFileName;
|
||||
std::string mUtf8FileName;
|
||||
};
|
||||
|
||||
std::string readContent(const std::string& fileName)
|
||||
{
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ios::failbit | std::ios::badbit);
|
||||
file.open(std::string(OPENMW_TEST_SUITE_SOURCE_DIR) + "/toutf8/data/" + fileName);
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
struct Utf8EncoderTest : TestWithParam<Params> {};
|
||||
|
||||
TEST(Utf8EncoderTest, getUtf8ShouldReturnEmptyAsIs)
|
||||
{
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
EXPECT_EQ(encoder.getUtf8(std::string_view()), std::string_view());
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getUtf8ShouldReturnAsciiOnlyAsIs)
|
||||
{
|
||||
std::string input;
|
||||
for (int c = 1; c <= std::numeric_limits<char>::max(); ++c)
|
||||
input.push_back(c);
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
const std::string_view result = encoder.getUtf8(input);
|
||||
EXPECT_EQ(result.data(), input.data());
|
||||
EXPECT_EQ(result.size(), input.size());
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilZero)
|
||||
{
|
||||
const std::string input("a\0b");
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
const std::string_view result = encoder.getUtf8(input);
|
||||
EXPECT_EQ(result, "a");
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForAscii)
|
||||
{
|
||||
const std::string input("abc");
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2));
|
||||
EXPECT_EQ(result, "ab");
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getUtf8ShouldLookUpUntilEndOfInputForNonAscii)
|
||||
{
|
||||
const std::string input("a\x92" "b");
|
||||
Utf8Encoder encoder(FromType::WINDOWS_1252);
|
||||
const std::string_view result = encoder.getUtf8(std::string_view(input.data(), 2));
|
||||
EXPECT_EQ(result, "a\xE2\x80\x99");
|
||||
}
|
||||
|
||||
TEST_P(Utf8EncoderTest, getUtf8ShouldConvertFromLegacyEncodingToUtf8)
|
||||
{
|
||||
const std::string input(readContent(GetParam().mLegacyEncodingFileName));
|
||||
const std::string expected(readContent(GetParam().mUtf8FileName));
|
||||
Utf8Encoder encoder(GetParam().mLegacyEncoding);
|
||||
const std::string_view result = encoder.getUtf8(input);
|
||||
EXPECT_EQ(result, expected);
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getLegacyEncShouldReturnEmptyAsIs)
|
||||
{
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
EXPECT_EQ(encoder.getLegacyEnc(std::string_view()), std::string_view());
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getLegacyEncShouldReturnAsciiOnlyAsIs)
|
||||
{
|
||||
std::string input;
|
||||
for (int c = 1; c <= std::numeric_limits<char>::max(); ++c)
|
||||
input.push_back(c);
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
const std::string_view result = encoder.getLegacyEnc(input);
|
||||
EXPECT_EQ(result.data(), input.data());
|
||||
EXPECT_EQ(result.size(), input.size());
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilZero)
|
||||
{
|
||||
const std::string input("a\0b");
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
const std::string_view result = encoder.getLegacyEnc(input);
|
||||
EXPECT_EQ(result, "a");
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getLegacyEncShouldLookUpUntilEndOfInputForAscii)
|
||||
{
|
||||
const std::string input("abc");
|
||||
Utf8Encoder encoder(FromType::CP437);
|
||||
const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 2));
|
||||
EXPECT_EQ(result, "ab");
|
||||
}
|
||||
|
||||
TEST(Utf8EncoderTest, getLegacyEncShouldStripIncompleteCharacters)
|
||||
{
|
||||
const std::string input("a\xc3\xa2\xe2\x80\x99");
|
||||
Utf8Encoder encoder(FromType::WINDOWS_1252);
|
||||
const std::string_view result = encoder.getLegacyEnc(std::string_view(input.data(), 5));
|
||||
EXPECT_EQ(result, "a\xe2");
|
||||
}
|
||||
|
||||
TEST_P(Utf8EncoderTest, getLegacyEncShouldConvertFromUtf8ToLegacyEncoding)
|
||||
{
|
||||
const std::string input(readContent(GetParam().mUtf8FileName));
|
||||
const std::string expected(readContent(GetParam().mLegacyEncodingFileName));
|
||||
Utf8Encoder encoder(GetParam().mLegacyEncoding);
|
||||
const std::string_view result = encoder.getLegacyEnc(input);
|
||||
EXPECT_EQ(result, expected);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(Files, Utf8EncoderTest, Values(
|
||||
Params {ToUTF8::WINDOWS_1251, "russian-win1251.txt", "russian-utf8.txt"},
|
||||
Params {ToUTF8::WINDOWS_1252, "french-win1252.txt", "french-utf8.txt"}
|
||||
));
|
||||
|
||||
TEST(StatelessUtf8EncoderTest, shouldCleanupBuffer)
|
||||
{
|
||||
std::string buffer;
|
||||
StatelessUtf8Encoder encoder(FromType::WINDOWS_1252);
|
||||
encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::UseGrowFactor, buffer);
|
||||
const std::string shortString("short\x92");
|
||||
ASSERT_GT(buffer.size(), shortString.size());
|
||||
const std::string_view shortUtf8 = encoder.getUtf8(shortString, BufferAllocationPolicy::UseGrowFactor, buffer);
|
||||
ASSERT_GE(buffer.size(), shortUtf8.size());
|
||||
EXPECT_EQ(buffer[shortUtf8.size()], '\0') << buffer;
|
||||
}
|
||||
|
||||
TEST(StatelessUtf8EncoderTest, withFitToRequiredSizeShouldResizeBuffer)
|
||||
{
|
||||
std::string buffer;
|
||||
StatelessUtf8Encoder encoder(FromType::WINDOWS_1252);
|
||||
const std::string_view utf8 = encoder.getUtf8(std::string_view("long string\x92"), BufferAllocationPolicy::FitToRequiredSize, buffer);
|
||||
EXPECT_EQ(buffer.size(), utf8.size());
|
||||
}
|
||||
}
|
|
@ -76,7 +76,7 @@ add_component_dir (to_utf8
|
|||
to_utf8
|
||||
)
|
||||
|
||||
add_component_dir(esm attr defs esmcommon records util luascripts)
|
||||
add_component_dir(esm attr common defs esmcommon reader records util luascripts)
|
||||
|
||||
add_component_dir (esm3
|
||||
esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell
|
||||
|
@ -94,6 +94,16 @@ add_component_dir (esm3terrain
|
|||
storage
|
||||
)
|
||||
|
||||
add_component_dir (esm4
|
||||
loadachr loadacre loadacti loadalch loadaloc loadammo loadanio loadappa loadarma loadarmo loadaspc loadbook
|
||||
loadbptd loadcell loadclas loadclfm loadclot common loadcont loadcrea loaddial loaddobj loaddoor loadeyes
|
||||
loadflor loadflst formid loadfurn loadglob loadgras loadhair loadhdpt loadidle loadidlm loadimod loadinfo
|
||||
loadingr loadkeym loadland loadlgtm loadligh loadltex loadlvlc loadlvli loadlvln loadmato loadmisc loadmset
|
||||
loadmstt loadmusc loadnavi loadnavm loadnote loadnpc loadotft loadpack loadpgrd loadpgre loadpwat loadqust
|
||||
loadrace loadrefr loadregn loadroad loadsbsp loadscol loadscpt loadscrl loadsgst loadslgm loadsndr
|
||||
loadsoun loadstat loadtact loadterm loadtes4 loadtree loadtxst loadweap loadwrld reader
|
||||
)
|
||||
|
||||
add_component_dir (misc
|
||||
constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread
|
||||
compression osguservalues errorMarker color
|
||||
|
|
|
@ -445,8 +445,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path)
|
|||
|
||||
try {
|
||||
ESM::ESMReader fileReader;
|
||||
ToUTF8::Utf8Encoder encoder =
|
||||
ToUTF8::calculateEncoding(mEncoding.toStdString());
|
||||
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString()));
|
||||
fileReader.setEncoder(&encoder);
|
||||
fileReader.open(std::string(dir.absoluteFilePath(path2).toUtf8().constData()));
|
||||
|
||||
|
|
15
components/esm/common.cpp
Normal file
15
components/esm/common.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include "sstream"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
std::string printName(const std::uint32_t typeId)
|
||||
{
|
||||
unsigned char typeName[4];
|
||||
typeName[0] = typeId & 0xff;
|
||||
typeName[1] = (typeId >> 8) & 0xff;
|
||||
typeName[2] = (typeId >> 16) & 0xff;
|
||||
typeName[3] = (typeId >> 24) & 0xff;
|
||||
|
||||
return std::string((char*)typeName, 4);
|
||||
}
|
||||
}
|
57
components/esm/common.hpp
Normal file
57
components/esm/common.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef COMPONENT_ESM_COMMON_H
|
||||
#define COMPONENT_ESM_COMMON_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
union ESMVersion
|
||||
{
|
||||
float f;
|
||||
std::uint32_t ui;
|
||||
};
|
||||
|
||||
union TypeId
|
||||
{
|
||||
std::uint32_t value;
|
||||
char name[4]; // record type in ascii
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
enum ESMVersions
|
||||
{
|
||||
VER_120 = 0x3f99999a, // TES3
|
||||
VER_130 = 0x3fa66666, // TES3
|
||||
VER_080 = 0x3f4ccccd, // TES4
|
||||
VER_100 = 0x3f800000, // TES4
|
||||
VER_132 = 0x3fa8f5c3, // FONV Courier's Stash, DeadMoney
|
||||
VER_133 = 0x3faa3d71, // FONV HonestHearts
|
||||
VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues
|
||||
VER_094 = 0x3f70a3d7, // TES5/FO3
|
||||
VER_170 = 0x3fd9999a // TES5
|
||||
};
|
||||
|
||||
// Defines another files (esm or esp) that this file depends upon.
|
||||
struct MasterData
|
||||
{
|
||||
std::string name;
|
||||
std::uint64_t size;
|
||||
};
|
||||
|
||||
enum VarType
|
||||
{
|
||||
VT_Unknown = 0,
|
||||
VT_None,
|
||||
VT_Short, // stored as a float, kinda
|
||||
VT_Int,
|
||||
VT_Long, // stored as a float
|
||||
VT_Float,
|
||||
VT_String
|
||||
};
|
||||
|
||||
std::string printName(const std::uint32_t typeId);
|
||||
}
|
||||
|
||||
#endif // COMPONENT_ESM_COMMON_H
|
|
@ -6,6 +6,8 @@
|
|||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -30,6 +32,41 @@ struct FixedString
|
|||
|
||||
char mData[capacity];
|
||||
|
||||
FixedString() = default;
|
||||
|
||||
template <std::size_t size>
|
||||
constexpr FixedString(const char (&value)[size]) noexcept
|
||||
: mData()
|
||||
{
|
||||
if constexpr (capacity == sizeof(std::uint32_t))
|
||||
{
|
||||
static_assert(capacity == size || capacity + 1 == size);
|
||||
if constexpr (capacity + 1 == size)
|
||||
assert(value[capacity] == '\0');
|
||||
for (std::size_t i = 0; i < capacity; ++i)
|
||||
mData[i] = value[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t length = std::min(capacity, size);
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
mData[i] = value[i];
|
||||
mData[std::min(capacity - 1, length)] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
constexpr explicit FixedString(std::uint32_t value) noexcept
|
||||
: mData()
|
||||
{
|
||||
static_assert(capacity == sizeof(std::uint32_t));
|
||||
for (std::size_t i = 0; i < capacity; ++i)
|
||||
mData[i] = static_cast<char>((value >> (i * std::numeric_limits<std::uint8_t>::digits)) & std::numeric_limits<std::uint8_t>::max());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr explicit FixedString(T value) noexcept
|
||||
: FixedString(static_cast<std::uint32_t>(value)) {}
|
||||
|
||||
std::string_view toStringView() const noexcept
|
||||
{
|
||||
return std::string_view(mData, strnlen(mData, capacity));
|
||||
|
@ -116,6 +153,11 @@ inline bool operator==(const FixedString<4>& lhs, std::uint32_t rhs) noexcept
|
|||
return lhs.toInt() == rhs;
|
||||
}
|
||||
|
||||
inline bool operator==(const FixedString<4>& lhs, const FixedString<4>& rhs) noexcept
|
||||
{
|
||||
return lhs.toInt() == rhs.toInt();
|
||||
}
|
||||
|
||||
template <std::size_t capacity, class Rhs>
|
||||
inline bool operator!=(const FixedString<capacity>& lhs, const Rhs& rhs) noexcept
|
||||
{
|
||||
|
|
86
components/esm/reader.cpp
Normal file
86
components/esm/reader.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#include "reader.hpp"
|
||||
|
||||
//#ifdef NDEBUG
|
||||
//#undef NDEBUG
|
||||
//#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <components/files/constrainedfilestream.hpp>
|
||||
|
||||
#include "components/esm3/esmreader.hpp"
|
||||
#include "components/esm4/reader.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
Reader* Reader::getReader(const std::string &filename)
|
||||
{
|
||||
Files::IStreamPtr esmStream(Files::openConstrainedFileStream (filename.c_str ()));
|
||||
|
||||
std::uint32_t modVer = 0; // get the first 4 bytes of the record header only
|
||||
esmStream->read((char*)&modVer, sizeof(modVer));
|
||||
if (esmStream->gcount() == sizeof(modVer))
|
||||
{
|
||||
esmStream->seekg(0);
|
||||
|
||||
if (modVer == ESM4::REC_TES4)
|
||||
{
|
||||
return new ESM4::Reader(esmStream, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
//return new ESM3::ESMReader(esmStream, filename);
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown file format");
|
||||
}
|
||||
|
||||
bool Reader::getStringImpl(std::string& str, std::size_t size,
|
||||
Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull)
|
||||
{
|
||||
std::size_t newSize = size;
|
||||
|
||||
if (encoder)
|
||||
{
|
||||
std::string input(size, '\0');
|
||||
filestream->read(input.data(), size);
|
||||
if (filestream->gcount() == static_cast<std::streamsize>(size))
|
||||
{
|
||||
encoder->getUtf8(input, ToUTF8::BufferAllocationPolicy::FitToRequiredSize, str);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasNull)
|
||||
newSize -= 1; // don't read the null terminator yet
|
||||
|
||||
str.resize(newSize); // assumed C++11
|
||||
filestream->read(&str[0], newSize);
|
||||
if ((std::size_t)filestream->gcount() == newSize)
|
||||
{
|
||||
if (hasNull)
|
||||
{
|
||||
char ch;
|
||||
filestream->read(&ch, 1); // read the null terminator
|
||||
assert (ch == '\0'
|
||||
&& "ESM4::Reader::getString string is not terminated with a null");
|
||||
}
|
||||
#if 0
|
||||
else
|
||||
{
|
||||
// NOTE: normal ESMs don't but omwsave has locals or spells with null terminator
|
||||
assert (str[newSize - 1] != '\0'
|
||||
&& "ESM4::Reader::getString string is unexpectedly terminated with a null");
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
str.clear();
|
||||
return false; // FIXME: throw instead?
|
||||
}
|
||||
}
|
60
components/esm/reader.hpp
Normal file
60
components/esm/reader.hpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef COMPONENT_ESM_READER_H
|
||||
#define COMPONENT_ESM_READER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <components/files/constrainedfilestream.hpp>
|
||||
#include <components/to_utf8/to_utf8.hpp>
|
||||
|
||||
#include "common.hpp" // MasterData
|
||||
|
||||
namespace ToUTF8
|
||||
{
|
||||
class Utf8Encoder;
|
||||
}
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class Reader
|
||||
{
|
||||
std::vector<Reader*>* mGlobalReaderList;
|
||||
|
||||
public:
|
||||
virtual ~Reader() {}
|
||||
|
||||
static Reader* getReader(const std::string& filename);
|
||||
|
||||
void setGlobalReaderList(std::vector<Reader*> *list) {mGlobalReaderList = list;}
|
||||
std::vector<Reader*> *getGlobalReaderList() {return mGlobalReaderList;}
|
||||
|
||||
virtual inline bool isEsm4() const = 0;
|
||||
|
||||
virtual inline bool hasMoreRecs() const = 0;
|
||||
|
||||
virtual inline void setEncoder(ToUTF8::StatelessUtf8Encoder* encoder) = 0;
|
||||
|
||||
// used to check for dependencies e.g. CS::Editor::run()
|
||||
virtual inline const std::vector<ESM::MasterData>& getGameFiles() const = 0;
|
||||
|
||||
// used by ContentSelector::ContentModel::addFiles()
|
||||
virtual inline const std::string getAuthor() const = 0;
|
||||
virtual inline const std::string getDesc() const = 0;
|
||||
virtual inline int getFormat() const = 0;
|
||||
|
||||
virtual inline std::string getFileName() const = 0;
|
||||
|
||||
// used by CSMWorld::Data::startLoading() and getTotalRecords() for loading progress bar
|
||||
virtual inline int getRecordCount() const = 0;
|
||||
|
||||
virtual void setModIndex(std::uint32_t index) = 0;
|
||||
|
||||
// used by CSMWorld::Data::getTotalRecords()
|
||||
virtual void close() = 0;
|
||||
|
||||
protected:
|
||||
bool getStringImpl(std::string& str, std::size_t size,
|
||||
Files::IStreamPtr filestream, ToUTF8::StatelessUtf8Encoder* encoder, bool hasNull = false);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // COMPONENT_ESM_READER_H
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
void save(ESM::ESMWriter& esm, const std::vector<ESM::ActiveSpells::ActiveSpellParams>& spells, const std::string& tag)
|
||||
void save(ESM::ESMWriter& esm, const std::vector<ESM::ActiveSpells::ActiveSpellParams>& spells, ESM::NAME tag)
|
||||
{
|
||||
for (const auto& params : spells)
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
void load(ESM::ESMReader& esm, std::vector<ESM::ActiveSpells::ActiveSpellParams>& spells, const char* tag)
|
||||
void load(ESM::ESMReader& esm, std::vector<ESM::ActiveSpells::ActiveSpellParams>& spells, ESM::NAME tag)
|
||||
{
|
||||
int format = esm.getFormat();
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ namespace ESM
|
|||
|
||||
case AI_Escort:
|
||||
case AI_Follow: {
|
||||
const char *name = (it->mType == AI_Escort) ? "AI_E" : "AI_F";
|
||||
const ESM::NAME name = (it->mType == AI_Escort) ? ESM::NAME("AI_E") : ESM::NAME("AI_F");
|
||||
esm.writeHNT(name, it->mTarget, sizeof(it->mTarget));
|
||||
esm.writeHNOCString("CNDT", it->mCellName);
|
||||
break;
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
#include "esmreader.hpp"
|
||||
#include "esmwriter.hpp"
|
||||
|
||||
void ESM::RefNum::load (ESMReader& esm, bool wide, const std::string& tag)
|
||||
void ESM::RefNum::load(ESMReader& esm, bool wide, ESM::NAME tag)
|
||||
{
|
||||
if (wide)
|
||||
esm.getHNT (*this, tag.c_str(), 8);
|
||||
esm.getHNT(*this, tag, 8);
|
||||
else
|
||||
esm.getHNT (mIndex, tag.c_str());
|
||||
esm.getHNT(mIndex, tag);
|
||||
}
|
||||
|
||||
void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const
|
||||
void ESM::RefNum::save(ESMWriter &esm, bool wide, ESM::NAME tag) const
|
||||
{
|
||||
if (wide)
|
||||
esm.writeHNT (tag, *this, 8);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <string>
|
||||
|
||||
#include "components/esm/defs.hpp"
|
||||
#include "components/esm/esmcommon.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -18,9 +19,9 @@ namespace ESM
|
|||
unsigned int mIndex;
|
||||
int mContentFile;
|
||||
|
||||
void load (ESMReader& esm, bool wide = false, const std::string& tag = "FRMR");
|
||||
void load(ESMReader& esm, bool wide = false, ESM::NAME tag = "FRMR");
|
||||
|
||||
void save (ESMWriter &esm, bool wide = false, const std::string& tag = "FRMR") const;
|
||||
void save(ESMWriter &esm, bool wide = false, ESM::NAME tag = "FRMR") const;
|
||||
|
||||
inline bool hasContentFile() const { return mContentFile >= 0; }
|
||||
|
||||
|
|
|
@ -113,14 +113,14 @@ void ESMReader::open(const std::string &file)
|
|||
open (Files::openConstrainedFileStream (file.c_str ()), file);
|
||||
}
|
||||
|
||||
std::string ESMReader::getHNOString(const char* name)
|
||||
std::string ESMReader::getHNOString(NAME name)
|
||||
{
|
||||
if (isNextSub(name))
|
||||
return getHString();
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ESMReader::getHNString(const char* name)
|
||||
std::string ESMReader::getHNString(NAME name)
|
||||
{
|
||||
getSubNameIs(name);
|
||||
return getHString();
|
||||
|
@ -156,21 +156,21 @@ void ESMReader::getHExact(void*p, int size)
|
|||
}
|
||||
|
||||
// Read the given number of bytes from a named subrecord
|
||||
void ESMReader::getHNExact(void*p, int size, const char* name)
|
||||
void ESMReader::getHNExact(void*p, int size, NAME name)
|
||||
{
|
||||
getSubNameIs(name);
|
||||
getHExact(p, size);
|
||||
}
|
||||
|
||||
// Get the next subrecord name and check if it matches the parameter
|
||||
void ESMReader::getSubNameIs(const char* name)
|
||||
void ESMReader::getSubNameIs(NAME name)
|
||||
{
|
||||
getSubName();
|
||||
if (mCtx.subName != name)
|
||||
fail("Expected subrecord " + std::string(name) + " but got " + mCtx.subName.toString());
|
||||
fail("Expected subrecord " + name.toString() + " but got " + mCtx.subName.toString());
|
||||
}
|
||||
|
||||
bool ESMReader::isNextSub(const char* name)
|
||||
bool ESMReader::isNextSub(NAME name)
|
||||
{
|
||||
if (!hasMoreSubs())
|
||||
return false;
|
||||
|
@ -185,7 +185,7 @@ bool ESMReader::isNextSub(const char* name)
|
|||
return !mCtx.subCached;
|
||||
}
|
||||
|
||||
bool ESMReader::peekNextSub(const char *name)
|
||||
bool ESMReader::peekNextSub(NAME name)
|
||||
{
|
||||
if (!hasMoreSubs())
|
||||
return false;
|
||||
|
@ -226,7 +226,7 @@ void ESMReader::skipHSubSize(int size)
|
|||
reportSubSizeMismatch(mCtx.leftSub, size);
|
||||
}
|
||||
|
||||
void ESMReader::skipHSubUntil(const char *name)
|
||||
void ESMReader::skipHSubUntil(NAME name)
|
||||
{
|
||||
while (hasMoreSubs() && !isNextSub(name))
|
||||
{
|
||||
|
@ -320,7 +320,7 @@ std::string ESMReader::getString(int size)
|
|||
|
||||
// Convert to UTF8 and return
|
||||
if (mEncoder)
|
||||
return mEncoder->getUtf8(ptr, size);
|
||||
return std::string(mEncoder->getUtf8(std::string_view(ptr, size)));
|
||||
|
||||
return std::string (ptr, size);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ public:
|
|||
|
||||
// Read data of a given type, stored in a subrecord of a given name
|
||||
template <typename X>
|
||||
void getHNT(X &x, const char* name)
|
||||
void getHNT(X &x, NAME name)
|
||||
{
|
||||
getSubNameIs(name);
|
||||
getHT(x);
|
||||
|
@ -106,7 +106,7 @@ public:
|
|||
|
||||
// Optional version of getHNT
|
||||
template <typename X>
|
||||
void getHNOT(X &x, const char* name)
|
||||
void getHNOT(X &x, NAME name)
|
||||
{
|
||||
if(isNextSub(name))
|
||||
getHT(x);
|
||||
|
@ -115,7 +115,7 @@ public:
|
|||
// Version with extra size checking, to make sure the compiler
|
||||
// doesn't mess up our struct padding.
|
||||
template <typename X>
|
||||
void getHNT(X &x, const char* name, int size)
|
||||
void getHNT(X &x, NAME name, int size)
|
||||
{
|
||||
assert(sizeof(X) == size);
|
||||
getSubNameIs(name);
|
||||
|
@ -123,7 +123,7 @@ public:
|
|||
}
|
||||
|
||||
template <typename X>
|
||||
void getHNOT(X &x, const char* name, int size)
|
||||
void getHNOT(X &x, NAME name, int size)
|
||||
{
|
||||
assert(sizeof(X) == size);
|
||||
if(isNextSub(name))
|
||||
|
@ -150,10 +150,10 @@ public:
|
|||
}
|
||||
|
||||
// Read a string by the given name if it is the next record.
|
||||
std::string getHNOString(const char* name);
|
||||
std::string getHNOString(NAME name);
|
||||
|
||||
// Read a string with the given sub-record name
|
||||
std::string getHNString(const char* name);
|
||||
std::string getHNString(NAME name);
|
||||
|
||||
// Read a string, including the sub-record header (but not the name)
|
||||
std::string getHString();
|
||||
|
@ -162,7 +162,7 @@ public:
|
|||
void getHExact(void*p, int size);
|
||||
|
||||
// Read the given number of bytes from a named subrecord
|
||||
void getHNExact(void*p, int size, const char* name);
|
||||
void getHNExact(void*p, int size, NAME name);
|
||||
|
||||
/*************************************************************************
|
||||
*
|
||||
|
@ -171,16 +171,16 @@ public:
|
|||
*************************************************************************/
|
||||
|
||||
// Get the next subrecord name and check if it matches the parameter
|
||||
void getSubNameIs(const char* name);
|
||||
void getSubNameIs(NAME name);
|
||||
|
||||
/** Checks if the next sub record name matches the parameter. If it
|
||||
does, it is read into 'subName' just as if getSubName() was
|
||||
called. If not, the read name will still be available for future
|
||||
calls to getSubName(), isNextSub() and getSubNameIs().
|
||||
*/
|
||||
bool isNextSub(const char* name);
|
||||
bool isNextSub(NAME name);
|
||||
|
||||
bool peekNextSub(const char* name);
|
||||
bool peekNextSub(NAME name);
|
||||
|
||||
// Store the current subrecord name for the next call of getSubName()
|
||||
void cacheSubName() {mCtx.subCached = true; };
|
||||
|
@ -197,7 +197,7 @@ public:
|
|||
void skipHSubSize(int size);
|
||||
|
||||
// Skip all subrecords until the given subrecord or no more subrecords remaining
|
||||
void skipHSubUntil(const char* name);
|
||||
void skipHSubUntil(NAME name);
|
||||
|
||||
/* Sub-record header. This updates leftRec beyond the current
|
||||
sub-record as well. leftSub contains size of current sub-record.
|
||||
|
|
|
@ -86,7 +86,7 @@ namespace ESM
|
|||
throw std::runtime_error ("Unclosed record remaining");
|
||||
}
|
||||
|
||||
void ESMWriter::startRecord(const std::string& name, uint32_t flags)
|
||||
void ESMWriter::startRecord(ESM::NAME name, uint32_t flags)
|
||||
{
|
||||
mRecordCount++;
|
||||
|
||||
|
@ -105,15 +105,10 @@ namespace ESM
|
|||
|
||||
void ESMWriter::startRecord (uint32_t name, uint32_t flags)
|
||||
{
|
||||
std::string type;
|
||||
for (int i=0; i<4; ++i)
|
||||
/// \todo make endianess agnostic
|
||||
type += reinterpret_cast<const char *> (&name)[i];
|
||||
|
||||
startRecord (type, flags);
|
||||
startRecord(ESM::NAME(name), flags);
|
||||
}
|
||||
|
||||
void ESMWriter::startSubRecord(const std::string& name)
|
||||
void ESMWriter::startSubRecord(ESM::NAME name)
|
||||
{
|
||||
// Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later.
|
||||
assert (mRecords.size() <= 1);
|
||||
|
@ -129,7 +124,7 @@ namespace ESM
|
|||
assert(mRecords.back().size == 0);
|
||||
}
|
||||
|
||||
void ESMWriter::endRecord(const std::string& name)
|
||||
void ESMWriter::endRecord(ESM::NAME name)
|
||||
{
|
||||
RecordData rec = mRecords.back();
|
||||
assert(rec.name == name);
|
||||
|
@ -147,22 +142,17 @@ namespace ESM
|
|||
|
||||
void ESMWriter::endRecord (uint32_t name)
|
||||
{
|
||||
std::string type;
|
||||
for (int i=0; i<4; ++i)
|
||||
/// \todo make endianess agnostic
|
||||
type += reinterpret_cast<const char *> (&name)[i];
|
||||
|
||||
endRecord (type);
|
||||
endRecord(ESM::NAME(name));
|
||||
}
|
||||
|
||||
void ESMWriter::writeHNString(const std::string& name, const std::string& data)
|
||||
void ESMWriter::writeHNString(ESM::NAME name, const std::string& data)
|
||||
{
|
||||
startSubRecord(name);
|
||||
writeHString(data);
|
||||
endRecord(name);
|
||||
}
|
||||
|
||||
void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size)
|
||||
void ESMWriter::writeHNString(ESM::NAME name, const std::string& data, size_t size)
|
||||
{
|
||||
assert(data.size() <= size);
|
||||
startSubRecord(name);
|
||||
|
@ -177,7 +167,7 @@ namespace ESM
|
|||
endRecord(name);
|
||||
}
|
||||
|
||||
void ESMWriter::writeFixedSizeString(const std::string &data, int size)
|
||||
void ESMWriter::writeFixedSizeString(const std::string& data, int size)
|
||||
{
|
||||
std::string string;
|
||||
if (!data.empty())
|
||||
|
@ -193,9 +183,9 @@ namespace ESM
|
|||
else
|
||||
{
|
||||
// Convert to UTF8 and return
|
||||
std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data;
|
||||
const std::string_view string = mEncoder != nullptr ? mEncoder->getLegacyEnc(data) : data;
|
||||
|
||||
write(string.c_str(), string.size());
|
||||
write(string.data(), string.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,10 +196,9 @@ namespace ESM
|
|||
write("\0", 1);
|
||||
}
|
||||
|
||||
void ESMWriter::writeName(const std::string& name)
|
||||
void ESMWriter::writeName(ESM::NAME name)
|
||||
{
|
||||
assert((name.size() == 4 && name[3] != '\0'));
|
||||
write(name.c_str(), name.size());
|
||||
write(name.mData, ESM::NAME::sCapacity);
|
||||
}
|
||||
|
||||
void ESMWriter::write(const char* data, size_t size)
|
||||
|
|
|
@ -19,7 +19,7 @@ class ESMWriter
|
|||
{
|
||||
struct RecordData
|
||||
{
|
||||
std::string name;
|
||||
ESM::NAME name;
|
||||
std::streampos position;
|
||||
uint32_t size;
|
||||
};
|
||||
|
@ -56,27 +56,27 @@ class ESMWriter
|
|||
void close();
|
||||
///< \note Does not close the stream.
|
||||
|
||||
void writeHNString(const std::string& name, const std::string& data);
|
||||
void writeHNString(const std::string& name, const std::string& data, size_t size);
|
||||
void writeHNCString(const std::string& name, const std::string& data)
|
||||
void writeHNString(ESM::NAME name, const std::string& data);
|
||||
void writeHNString(ESM::NAME name, const std::string& data, size_t size);
|
||||
void writeHNCString(ESM::NAME name, const std::string& data)
|
||||
{
|
||||
startSubRecord(name);
|
||||
writeHCString(data);
|
||||
endRecord(name);
|
||||
}
|
||||
void writeHNOString(const std::string& name, const std::string& data)
|
||||
void writeHNOString(ESM::NAME name, const std::string& data)
|
||||
{
|
||||
if (!data.empty())
|
||||
writeHNString(name, data);
|
||||
}
|
||||
void writeHNOCString(const std::string& name, const std::string& data)
|
||||
void writeHNOCString(ESM::NAME name, const std::string& data)
|
||||
{
|
||||
if (!data.empty())
|
||||
writeHNCString(name, data);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeHNT(const std::string& name, const T& data)
|
||||
void writeHNT(ESM::NAME name, const T& data)
|
||||
{
|
||||
startSubRecord(name);
|
||||
writeT(data);
|
||||
|
@ -84,7 +84,7 @@ class ESMWriter
|
|||
}
|
||||
|
||||
template<typename T, std::size_t size>
|
||||
void writeHNT(const std::string& name, const T (&data)[size])
|
||||
void writeHNT(ESM::NAME name, const T (&data)[size])
|
||||
{
|
||||
startSubRecord(name);
|
||||
writeT(data);
|
||||
|
@ -94,15 +94,15 @@ class ESMWriter
|
|||
// Prevent using writeHNT with strings. This already happened by accident and results in
|
||||
// state being discarded without any error on writing or reading it. :(
|
||||
// writeHNString and friends must be used instead.
|
||||
void writeHNT(const std::string& name, const std::string& data) = delete;
|
||||
void writeHNT(ESM::NAME name, const std::string& data) = delete;
|
||||
|
||||
void writeT(const std::string& data) = delete;
|
||||
void writeT(ESM::NAME data) = delete;
|
||||
|
||||
template<typename T, std::size_t size>
|
||||
void writeHNT(const std::string& name, const T (&data)[size], int) = delete;
|
||||
void writeHNT(ESM::NAME name, const T (&data)[size], int) = delete;
|
||||
|
||||
template<typename T>
|
||||
void writeHNT(const std::string& name, const T& data, int size)
|
||||
void writeHNT(ESM::NAME name, const T& data, int size)
|
||||
{
|
||||
startSubRecord(name);
|
||||
writeT(data, size);
|
||||
|
@ -129,16 +129,16 @@ class ESMWriter
|
|||
write((char*)&data, size);
|
||||
}
|
||||
|
||||
void startRecord(const std::string& name, uint32_t flags = 0);
|
||||
void startRecord(ESM::NAME name, uint32_t flags = 0);
|
||||
void startRecord(uint32_t name, uint32_t flags = 0);
|
||||
/// @note Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later.
|
||||
void startSubRecord(const std::string& name);
|
||||
void endRecord(const std::string& name);
|
||||
void startSubRecord(ESM::NAME name);
|
||||
void endRecord(ESM::NAME name);
|
||||
void endRecord(uint32_t name);
|
||||
void writeFixedSizeString(const std::string& data, int size);
|
||||
void writeHString(const std::string& data);
|
||||
void writeHCString(const std::string& data);
|
||||
void writeName(const std::string& data);
|
||||
void writeName(ESM::NAME data);
|
||||
void write(const char* data, size_t size);
|
||||
|
||||
private:
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <components/esm/esmcommon.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
||||
|
@ -27,7 +29,7 @@ struct LevelledListBase
|
|||
|
||||
// Record name used to read references. Must be set before load() is
|
||||
// called.
|
||||
const char *mRecName;
|
||||
ESM::NAME mRecName;
|
||||
|
||||
struct LevelItem
|
||||
{
|
||||
|
@ -37,6 +39,8 @@ struct LevelledListBase
|
|||
|
||||
std::vector<LevelItem> mList;
|
||||
|
||||
explicit LevelledListBase(ESM::NAME recName) : mRecName(recName) {}
|
||||
|
||||
void load(ESMReader &esm, bool &isDeleted);
|
||||
void save(ESMWriter &esm, bool isDeleted = false) const;
|
||||
|
||||
|
@ -58,10 +62,7 @@ struct CreatureLevList: LevelledListBase
|
|||
// player.
|
||||
};
|
||||
|
||||
CreatureLevList()
|
||||
{
|
||||
mRecName = "CNAM";
|
||||
}
|
||||
CreatureLevList() : LevelledListBase("CNAM") {}
|
||||
};
|
||||
|
||||
struct ItemLevList: LevelledListBase
|
||||
|
@ -84,10 +85,7 @@ struct ItemLevList: LevelledListBase
|
|||
// player.
|
||||
};
|
||||
|
||||
ItemLevList()
|
||||
{
|
||||
mRecName = "INAM";
|
||||
}
|
||||
ItemLevList() : LevelledListBase("INAM") {}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "esmwriter.hpp"
|
||||
|
||||
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
|
||||
int ESM::SavedGame::sCurrentFormat = 19;
|
||||
int ESM::SavedGame::sCurrentFormat = 20;
|
||||
|
||||
void ESM::SavedGame::load (ESMReader &esm)
|
||||
{
|
||||
|
|
|
@ -5,17 +5,17 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
const char* currentRegionRecord = "CREG";
|
||||
const char* timePassedRecord = "TMPS";
|
||||
const char* fastForwardRecord = "FAST";
|
||||
const char* weatherUpdateTimeRecord = "WUPD";
|
||||
const char* transitionFactorRecord = "TRFC";
|
||||
const char* currentWeatherRecord = "CWTH";
|
||||
const char* nextWeatherRecord = "NWTH";
|
||||
const char* queuedWeatherRecord = "QWTH";
|
||||
const char* regionNameRecord = "RGNN";
|
||||
const char* regionWeatherRecord = "RGNW";
|
||||
const char* regionChanceRecord = "RGNC";
|
||||
constexpr ESM::NAME currentRegionRecord = "CREG";
|
||||
constexpr ESM::NAME timePassedRecord = "TMPS";
|
||||
constexpr ESM::NAME fastForwardRecord = "FAST";
|
||||
constexpr ESM::NAME weatherUpdateTimeRecord = "WUPD";
|
||||
constexpr ESM::NAME transitionFactorRecord = "TRFC";
|
||||
constexpr ESM::NAME currentWeatherRecord = "CWTH";
|
||||
constexpr ESM::NAME nextWeatherRecord = "NWTH";
|
||||
constexpr ESM::NAME queuedWeatherRecord = "QWTH";
|
||||
constexpr ESM::NAME regionNameRecord = "RGNN";
|
||||
constexpr ESM::NAME regionWeatherRecord = "RGNW";
|
||||
constexpr ESM::NAME regionChanceRecord = "RGNC";
|
||||
}
|
||||
|
||||
namespace ESM
|
||||
|
|
70
components/esm4/acti.hpp
Normal file
70
components/esm4/acti.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ACTI_H
|
||||
#define ESM4_ACTI_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct Activator
|
||||
{
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
std::string mFullName;
|
||||
std::string mModel;
|
||||
|
||||
FormId mScriptId;
|
||||
FormId mLoopingSound; // SOUN
|
||||
FormId mActivationSound; // SOUN
|
||||
|
||||
float mBoundRadius;
|
||||
|
||||
FormId mRadioTemplate; // SOUN
|
||||
FormId mRadioStation; // TACT
|
||||
|
||||
std::string mActivationPrompt;
|
||||
|
||||
Activator();
|
||||
virtual ~Activator();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ACTI_H
|
163
components/esm4/actor.hpp
Normal file
163
components/esm4/actor.hpp
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
Copyright (C) 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ACTOR_H
|
||||
#define ESM4_ACTOR_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct AIData // NPC_, CREA
|
||||
{
|
||||
std::uint8_t aggression;
|
||||
std::uint8_t confidence;
|
||||
std::uint8_t energyLevel;
|
||||
std::uint8_t responsibility;
|
||||
std::uint32_t aiFlags;
|
||||
std::uint8_t trainSkill;
|
||||
std::uint8_t trainLevel;
|
||||
std::uint16_t unknown;
|
||||
};
|
||||
|
||||
struct AttributeValues
|
||||
{
|
||||
std::uint8_t strength;
|
||||
std::uint8_t intelligence;
|
||||
std::uint8_t willpower;
|
||||
std::uint8_t agility;
|
||||
std::uint8_t speed;
|
||||
std::uint8_t endurance;
|
||||
std::uint8_t personality;
|
||||
std::uint8_t luck;
|
||||
};
|
||||
|
||||
struct ACBS_TES4
|
||||
{
|
||||
std::uint32_t flags;
|
||||
std::uint16_t baseSpell;
|
||||
std::uint16_t fatigue;
|
||||
std::uint16_t barterGold;
|
||||
std::int16_t levelOrOffset;
|
||||
std::uint16_t calcMin;
|
||||
std::uint16_t calcMax;
|
||||
std::uint32_t padding1;
|
||||
std::uint32_t padding2;
|
||||
};
|
||||
|
||||
struct ACBS_FO3
|
||||
{
|
||||
std::uint32_t flags;
|
||||
std::uint16_t fatigue;
|
||||
std::uint16_t barterGold;
|
||||
std::int16_t levelOrMult;
|
||||
std::uint16_t calcMinlevel;
|
||||
std::uint16_t calcMaxlevel;
|
||||
std::uint16_t speedMultiplier;
|
||||
float karma;
|
||||
std::int16_t dispositionBase;
|
||||
std::uint16_t templateFlags;
|
||||
};
|
||||
|
||||
struct ACBS_TES5
|
||||
{
|
||||
std::uint32_t flags;
|
||||
std::uint16_t magickaOffset;
|
||||
std::uint16_t staminaOffset;
|
||||
std::uint16_t levelOrMult; // TODO: check if int16_t
|
||||
std::uint16_t calcMinlevel;
|
||||
std::uint16_t calcMaxlevel;
|
||||
std::uint16_t speedMultiplier;
|
||||
std::uint16_t dispositionBase; // TODO: check if int16_t
|
||||
std::uint16_t templateFlags;
|
||||
std::uint16_t healthOffset;
|
||||
std::uint16_t bleedoutOverride;
|
||||
};
|
||||
|
||||
union ActorBaseConfig
|
||||
{
|
||||
ACBS_TES4 tes4;
|
||||
ACBS_FO3 fo3;
|
||||
ACBS_TES5 tes5;
|
||||
};
|
||||
|
||||
struct ActorFaction
|
||||
{
|
||||
FormId faction;
|
||||
std::int8_t rank;
|
||||
std::uint8_t unknown1;
|
||||
std::uint8_t unknown2;
|
||||
std::uint8_t unknown3;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct BodyTemplate // TES5
|
||||
{
|
||||
// 0x00000001 - Head
|
||||
// 0x00000002 - Hair
|
||||
// 0x00000004 - Body
|
||||
// 0x00000008 - Hands
|
||||
// 0x00000010 - Forearms
|
||||
// 0x00000020 - Amulet
|
||||
// 0x00000040 - Ring
|
||||
// 0x00000080 - Feet
|
||||
// 0x00000100 - Calves
|
||||
// 0x00000200 - Shield
|
||||
// 0x00000400 - Tail
|
||||
// 0x00000800 - Long Hair
|
||||
// 0x00001000 - Circlet
|
||||
// 0x00002000 - Ears
|
||||
// 0x00004000 - Body AddOn 3
|
||||
// 0x00008000 - Body AddOn 4
|
||||
// 0x00010000 - Body AddOn 5
|
||||
// 0x00020000 - Body AddOn 6
|
||||
// 0x00040000 - Body AddOn 7
|
||||
// 0x00080000 - Body AddOn 8
|
||||
// 0x00100000 - Decapitate Head
|
||||
// 0x00200000 - Decapitate
|
||||
// 0x00400000 - Body AddOn 9
|
||||
// 0x00800000 - Body AddOn 10
|
||||
// 0x01000000 - Body AddOn 11
|
||||
// 0x02000000 - Body AddOn 12
|
||||
// 0x04000000 - Body AddOn 13
|
||||
// 0x08000000 - Body AddOn 14
|
||||
// 0x10000000 - Body AddOn 15
|
||||
// 0x20000000 - Body AddOn 16
|
||||
// 0x40000000 - Body AddOn 17
|
||||
// 0x80000000 - FX01
|
||||
std::uint32_t bodyPart;
|
||||
std::uint8_t flags;
|
||||
std::uint8_t unknown1; // probably padding
|
||||
std::uint8_t unknown2; // probably padding
|
||||
std::uint8_t unknown3; // probably padding
|
||||
std::uint32_t type; // 0 = light, 1 = heavy, 2 = none (cloth?)
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ACTOR_H
|
100
components/esm4/common.cpp
Normal file
100
components/esm4/common.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright (C) 2015-2016, 2018, 2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "common.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
const char *sGroupType[] =
|
||||
{
|
||||
"Record Type", "World Child", "Interior Cell", "Interior Sub Cell", "Exterior Cell",
|
||||
"Exterior Sub Cell", "Cell Child", "Topic Child", "Cell Persistent Child",
|
||||
"Cell Temporary Child", "Cell Visible Dist Child", "Unknown"
|
||||
};
|
||||
|
||||
std::string printLabel(const GroupLabel& label, const std::uint32_t type)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << std::string(sGroupType[std::min(type, (uint32_t)11)]); // avoid out of range
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ESM4::Grp_RecordType:
|
||||
{
|
||||
ss << ": " << std::string((char*)label.recordType, 4);
|
||||
break;
|
||||
}
|
||||
case ESM4::Grp_ExteriorCell:
|
||||
case ESM4::Grp_ExteriorSubCell:
|
||||
{
|
||||
//short x, y;
|
||||
//y = label & 0xff;
|
||||
//x = (label >> 16) & 0xff;
|
||||
ss << ": grid (x, y) " << std::dec << label.grid[1] << ", " << label.grid[0];
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::Grp_InteriorCell:
|
||||
case ESM4::Grp_InteriorSubCell:
|
||||
{
|
||||
ss << ": block 0x" << std::hex << label.value;
|
||||
break;
|
||||
}
|
||||
case ESM4::Grp_WorldChild:
|
||||
case ESM4::Grp_CellChild:
|
||||
case ESM4::Grp_TopicChild:
|
||||
case ESM4::Grp_CellPersistentChild:
|
||||
case ESM4::Grp_CellTemporaryChild:
|
||||
case ESM4::Grp_CellVisibleDistChild:
|
||||
{
|
||||
ss << ": FormId 0x" << formIdToString(label.value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void gridToString(std::int16_t x, std::int16_t y, std::string& str)
|
||||
{
|
||||
char buf[6+6+2+1]; // longest signed 16 bit number is 6 characters (-32768)
|
||||
int res = snprintf(buf, 6+6+2+1, "#%d %d", x, y);
|
||||
if (res > 0 && res < 6+6+2+1)
|
||||
str.assign(buf);
|
||||
else
|
||||
throw std::runtime_error("possible buffer overflow while converting grid");
|
||||
}
|
||||
}
|
939
components/esm4/common.hpp
Normal file
939
components/esm4/common.hpp
Normal file
|
@ -0,0 +1,939 @@
|
|||
/*
|
||||
Copyright (C) 2015-2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
MKTAG macro was adapated from ScummVM.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_COMMON_H
|
||||
#define ESM4_COMMON_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
// From ScummVM's endianness.h but for little endian
|
||||
#ifndef MKTAG
|
||||
#define MKTAG(a0,a1,a2,a3) ((std::uint32_t)((a0) | ((a1) << 8) | ((a2) << 16) | ((a3) << 24)))
|
||||
#endif
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
|
||||
// Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format
|
||||
enum RecordTypes
|
||||
{
|
||||
REC_AACT = MKTAG('A','A','C','T'), // Action
|
||||
REC_ACHR = MKTAG('A','C','H','R'), // Actor Reference
|
||||
REC_ACTI = MKTAG('A','C','T','I'), // Activator
|
||||
REC_ADDN = MKTAG('A','D','D','N'), // Addon Node
|
||||
REC_ALCH = MKTAG('A','L','C','H'), // Potion
|
||||
REC_AMMO = MKTAG('A','M','M','O'), // Ammo
|
||||
REC_ANIO = MKTAG('A','N','I','O'), // Animated Object
|
||||
REC_APPA = MKTAG('A','P','P','A'), // Apparatus (probably unused)
|
||||
REC_ARMA = MKTAG('A','R','M','A'), // Armature (Model)
|
||||
REC_ARMO = MKTAG('A','R','M','O'), // Armor
|
||||
REC_ARTO = MKTAG('A','R','T','O'), // Art Object
|
||||
REC_ASPC = MKTAG('A','S','P','C'), // Acoustic Space
|
||||
REC_ASTP = MKTAG('A','S','T','P'), // Association Type
|
||||
REC_AVIF = MKTAG('A','V','I','F'), // Actor Values/Perk Tree Graphics
|
||||
REC_BOOK = MKTAG('B','O','O','K'), // Book
|
||||
REC_BPTD = MKTAG('B','P','T','D'), // Body Part Data
|
||||
REC_CAMS = MKTAG('C','A','M','S'), // Camera Shot
|
||||
REC_CELL = MKTAG('C','E','L','L'), // Cell
|
||||
REC_CLAS = MKTAG('C','L','A','S'), // Class
|
||||
REC_CLFM = MKTAG('C','L','F','M'), // Color
|
||||
REC_CLMT = MKTAG('C','L','M','T'), // Climate
|
||||
REC_CLOT = MKTAG('C','L','O','T'), // Clothing
|
||||
REC_COBJ = MKTAG('C','O','B','J'), // Constructible Object (recipes)
|
||||
REC_COLL = MKTAG('C','O','L','L'), // Collision Layer
|
||||
REC_CONT = MKTAG('C','O','N','T'), // Container
|
||||
REC_CPTH = MKTAG('C','P','T','H'), // Camera Path
|
||||
REC_CREA = MKTAG('C','R','E','A'), // Creature
|
||||
REC_CSTY = MKTAG('C','S','T','Y'), // Combat Style
|
||||
REC_DEBR = MKTAG('D','E','B','R'), // Debris
|
||||
REC_DIAL = MKTAG('D','I','A','L'), // Dialog Topic
|
||||
REC_DLBR = MKTAG('D','L','B','R'), // Dialog Branch
|
||||
REC_DLVW = MKTAG('D','L','V','W'), // Dialog View
|
||||
REC_DOBJ = MKTAG('D','O','B','J'), // Default Object Manager
|
||||
REC_DOOR = MKTAG('D','O','O','R'), // Door
|
||||
REC_DUAL = MKTAG('D','U','A','L'), // Dual Cast Data (possibly unused)
|
||||
//REC_ECZN = MKTAG('E','C','Z','N'), // Encounter Zone
|
||||
REC_EFSH = MKTAG('E','F','S','H'), // Effect Shader
|
||||
REC_ENCH = MKTAG('E','N','C','H'), // Enchantment
|
||||
REC_EQUP = MKTAG('E','Q','U','P'), // Equip Slot (flag-type values)
|
||||
REC_EXPL = MKTAG('E','X','P','L'), // Explosion
|
||||
REC_EYES = MKTAG('E','Y','E','S'), // Eyes
|
||||
REC_FACT = MKTAG('F','A','C','T'), // Faction
|
||||
REC_FLOR = MKTAG('F','L','O','R'), // Flora
|
||||
REC_FLST = MKTAG('F','L','S','T'), // Form List (non-levelled list)
|
||||
REC_FSTP = MKTAG('F','S','T','P'), // Footstep
|
||||
REC_FSTS = MKTAG('F','S','T','S'), // Footstep Set
|
||||
REC_FURN = MKTAG('F','U','R','N'), // Furniture
|
||||
REC_GLOB = MKTAG('G','L','O','B'), // Global Variable
|
||||
REC_GMST = MKTAG('G','M','S','T'), // Game Setting
|
||||
REC_GRAS = MKTAG('G','R','A','S'), // Grass
|
||||
REC_GRUP = MKTAG('G','R','U','P'), // Form Group
|
||||
REC_HAIR = MKTAG('H','A','I','R'), // Hair
|
||||
//REC_HAZD = MKTAG('H','A','Z','D'), // Hazard
|
||||
REC_HDPT = MKTAG('H','D','P','T'), // Head Part
|
||||
REC_IDLE = MKTAG('I','D','L','E'), // Idle Animation
|
||||
REC_IDLM = MKTAG('I','D','L','M'), // Idle Marker
|
||||
REC_IMAD = MKTAG('I','M','A','D'), // Image Space Modifier
|
||||
REC_IMGS = MKTAG('I','M','G','S'), // Image Space
|
||||
REC_INFO = MKTAG('I','N','F','O'), // Dialog Topic Info
|
||||
REC_INGR = MKTAG('I','N','G','R'), // Ingredient
|
||||
REC_IPCT = MKTAG('I','P','C','T'), // Impact Data
|
||||
REC_IPDS = MKTAG('I','P','D','S'), // Impact Data Set
|
||||
REC_KEYM = MKTAG('K','E','Y','M'), // Key
|
||||
REC_KYWD = MKTAG('K','Y','W','D'), // Keyword
|
||||
REC_LAND = MKTAG('L','A','N','D'), // Land
|
||||
REC_LCRT = MKTAG('L','C','R','T'), // Location Reference Type
|
||||
REC_LCTN = MKTAG('L','C','T','N'), // Location
|
||||
REC_LGTM = MKTAG('L','G','T','M'), // Lighting Template
|
||||
REC_LIGH = MKTAG('L','I','G','H'), // Light
|
||||
REC_LSCR = MKTAG('L','S','C','R'), // Load Screen
|
||||
REC_LTEX = MKTAG('L','T','E','X'), // Land Texture
|
||||
REC_LVLC = MKTAG('L','V','L','C'), // Leveled Creature
|
||||
REC_LVLI = MKTAG('L','V','L','I'), // Leveled Item
|
||||
REC_LVLN = MKTAG('L','V','L','N'), // Leveled Actor
|
||||
REC_LVSP = MKTAG('L','V','S','P'), // Leveled Spell
|
||||
REC_MATO = MKTAG('M','A','T','O'), // Material Object
|
||||
REC_MATT = MKTAG('M','A','T','T'), // Material Type
|
||||
REC_MESG = MKTAG('M','E','S','G'), // Message
|
||||
REC_MGEF = MKTAG('M','G','E','F'), // Magic Effect
|
||||
REC_MISC = MKTAG('M','I','S','C'), // Misc. Object
|
||||
REC_MOVT = MKTAG('M','O','V','T'), // Movement Type
|
||||
REC_MSTT = MKTAG('M','S','T','T'), // Movable Static
|
||||
REC_MUSC = MKTAG('M','U','S','C'), // Music Type
|
||||
REC_MUST = MKTAG('M','U','S','T'), // Music Track
|
||||
REC_NAVI = MKTAG('N','A','V','I'), // Navigation (master data)
|
||||
REC_NAVM = MKTAG('N','A','V','M'), // Nav Mesh
|
||||
REC_NOTE = MKTAG('N','O','T','E'), // Note
|
||||
REC_NPC_ = MKTAG('N','P','C','_'), // Actor (NPC, Creature)
|
||||
REC_OTFT = MKTAG('O','T','F','T'), // Outfit
|
||||
REC_PACK = MKTAG('P','A','C','K'), // AI Package
|
||||
REC_PERK = MKTAG('P','E','R','K'), // Perk
|
||||
REC_PGRE = MKTAG('P','G','R','E'), // Placed grenade
|
||||
REC_PHZD = MKTAG('P','H','Z','D'), // Placed hazard
|
||||
REC_PROJ = MKTAG('P','R','O','J'), // Projectile
|
||||
REC_QUST = MKTAG('Q','U','S','T'), // Quest
|
||||
REC_RACE = MKTAG('R','A','C','E'), // Race / Creature type
|
||||
REC_REFR = MKTAG('R','E','F','R'), // Object Reference
|
||||
REC_REGN = MKTAG('R','E','G','N'), // Region (Audio/Weather)
|
||||
REC_RELA = MKTAG('R','E','L','A'), // Relationship
|
||||
REC_REVB = MKTAG('R','E','V','B'), // Reverb Parameters
|
||||
REC_RFCT = MKTAG('R','F','C','T'), // Visual Effect
|
||||
REC_SBSP = MKTAG('S','B','S','P'), // Subspace (TES4 only?)
|
||||
REC_SCEN = MKTAG('S','C','E','N'), // Scene
|
||||
REC_SCPT = MKTAG('S','C','P','T'), // Script
|
||||
REC_SCRL = MKTAG('S','C','R','L'), // Scroll
|
||||
REC_SGST = MKTAG('S','G','S','T'), // Sigil Stone
|
||||
REC_SHOU = MKTAG('S','H','O','U'), // Shout
|
||||
REC_SLGM = MKTAG('S','L','G','M'), // Soul Gem
|
||||
REC_SMBN = MKTAG('S','M','B','N'), // Story Manager Branch Node
|
||||
REC_SMEN = MKTAG('S','M','E','N'), // Story Manager Event Node
|
||||
REC_SMQN = MKTAG('S','M','Q','N'), // Story Manager Quest Node
|
||||
REC_SNCT = MKTAG('S','N','C','T'), // Sound Category
|
||||
REC_SNDR = MKTAG('S','N','D','R'), // Sound Reference
|
||||
REC_SOPM = MKTAG('S','O','P','M'), // Sound Output Model
|
||||
REC_SOUN = MKTAG('S','O','U','N'), // Sound
|
||||
REC_SPEL = MKTAG('S','P','E','L'), // Spell
|
||||
REC_SPGD = MKTAG('S','P','G','D'), // Shader Particle Geometry
|
||||
REC_STAT = MKTAG('S','T','A','T'), // Static
|
||||
REC_TACT = MKTAG('T','A','C','T'), // Talking Activator
|
||||
REC_TERM = MKTAG('T','E','R','M'), // Terminal
|
||||
REC_TES4 = MKTAG('T','E','S','4'), // Plugin info
|
||||
REC_TREE = MKTAG('T','R','E','E'), // Tree
|
||||
REC_TXST = MKTAG('T','X','S','T'), // Texture Set
|
||||
REC_VTYP = MKTAG('V','T','Y','P'), // Voice Type
|
||||
REC_WATR = MKTAG('W','A','T','R'), // Water Type
|
||||
REC_WEAP = MKTAG('W','E','A','P'), // Weapon
|
||||
REC_WOOP = MKTAG('W','O','O','P'), // Word Of Power
|
||||
REC_WRLD = MKTAG('W','R','L','D'), // World Space
|
||||
REC_WTHR = MKTAG('W','T','H','R'), // Weather
|
||||
REC_ACRE = MKTAG('A','C','R','E'), // Placed Creature (TES4 only?)
|
||||
REC_PGRD = MKTAG('P','G','R','D'), // Pathgrid (TES4 only?)
|
||||
REC_ROAD = MKTAG('R','O','A','D'), // Road (TES4 only?)
|
||||
REC_IMOD = MKTAG('I','M','O','D'), // Item Mod
|
||||
REC_PWAT = MKTAG('P','W','A','T'), // Placeable Water
|
||||
REC_SCOL = MKTAG('S','C','O','L'), // Static Collection
|
||||
REC_CCRD = MKTAG('C','C','R','D'), // Caravan Card
|
||||
REC_CMNY = MKTAG('C','M','N','Y'), // Caravan Money
|
||||
REC_ALOC = MKTAG('A','L','O','C'), // Audio Location Controller
|
||||
REC_MSET = MKTAG('M','S','E','T') // Media Set
|
||||
};
|
||||
|
||||
enum SubRecordTypes
|
||||
{
|
||||
SUB_HEDR = MKTAG('H','E','D','R'),
|
||||
SUB_CNAM = MKTAG('C','N','A','M'),
|
||||
SUB_SNAM = MKTAG('S','N','A','M'), // TES4 only?
|
||||
SUB_MAST = MKTAG('M','A','S','T'),
|
||||
SUB_DATA = MKTAG('D','A','T','A'),
|
||||
SUB_ONAM = MKTAG('O','N','A','M'),
|
||||
SUB_INTV = MKTAG('I','N','T','V'),
|
||||
SUB_INCC = MKTAG('I','N','C','C'),
|
||||
SUB_OFST = MKTAG('O','F','S','T'), // TES4 only?
|
||||
SUB_DELE = MKTAG('D','E','L','E'), // TES4 only?
|
||||
|
||||
SUB_DNAM = MKTAG('D','N','A','M'),
|
||||
SUB_EDID = MKTAG('E','D','I','D'),
|
||||
SUB_FULL = MKTAG('F','U','L','L'),
|
||||
SUB_LTMP = MKTAG('L','T','M','P'),
|
||||
SUB_MHDT = MKTAG('M','H','D','T'),
|
||||
SUB_MNAM = MKTAG('M','N','A','M'),
|
||||
SUB_MODL = MKTAG('M','O','D','L'),
|
||||
SUB_NAM0 = MKTAG('N','A','M','0'),
|
||||
SUB_NAM2 = MKTAG('N','A','M','2'),
|
||||
SUB_NAM3 = MKTAG('N','A','M','3'),
|
||||
SUB_NAM4 = MKTAG('N','A','M','4'),
|
||||
SUB_NAM9 = MKTAG('N','A','M','9'),
|
||||
SUB_NAMA = MKTAG('N','A','M','A'),
|
||||
SUB_PNAM = MKTAG('P','N','A','M'),
|
||||
SUB_RNAM = MKTAG('R','N','A','M'),
|
||||
SUB_TNAM = MKTAG('T','N','A','M'),
|
||||
SUB_UNAM = MKTAG('U','N','A','M'),
|
||||
SUB_WCTR = MKTAG('W','C','T','R'),
|
||||
SUB_WNAM = MKTAG('W','N','A','M'),
|
||||
SUB_XEZN = MKTAG('X','E','Z','N'),
|
||||
SUB_XLCN = MKTAG('X','L','C','N'),
|
||||
SUB_XXXX = MKTAG('X','X','X','X'),
|
||||
SUB_ZNAM = MKTAG('Z','N','A','M'),
|
||||
SUB_MODT = MKTAG('M','O','D','T'),
|
||||
SUB_ICON = MKTAG('I','C','O','N'), // TES4 only?
|
||||
|
||||
SUB_NVER = MKTAG('N','V','E','R'),
|
||||
SUB_NVMI = MKTAG('N','V','M','I'),
|
||||
SUB_NVPP = MKTAG('N','V','P','P'),
|
||||
SUB_NVSI = MKTAG('N','V','S','I'),
|
||||
|
||||
SUB_NVNM = MKTAG('N','V','N','M'),
|
||||
SUB_NNAM = MKTAG('N','N','A','M'),
|
||||
|
||||
SUB_XCLC = MKTAG('X','C','L','C'),
|
||||
SUB_XCLL = MKTAG('X','C','L','L'),
|
||||
SUB_TVDT = MKTAG('T','V','D','T'),
|
||||
SUB_XCGD = MKTAG('X','C','G','D'),
|
||||
SUB_LNAM = MKTAG('L','N','A','M'),
|
||||
SUB_XCLW = MKTAG('X','C','L','W'),
|
||||
SUB_XNAM = MKTAG('X','N','A','M'),
|
||||
SUB_XCLR = MKTAG('X','C','L','R'),
|
||||
SUB_XWCS = MKTAG('X','W','C','S'),
|
||||
SUB_XWCN = MKTAG('X','W','C','N'),
|
||||
SUB_XWCU = MKTAG('X','W','C','U'),
|
||||
SUB_XCWT = MKTAG('X','C','W','T'),
|
||||
SUB_XOWN = MKTAG('X','O','W','N'),
|
||||
SUB_XILL = MKTAG('X','I','L','L'),
|
||||
SUB_XWEM = MKTAG('X','W','E','M'),
|
||||
SUB_XCCM = MKTAG('X','C','C','M'),
|
||||
SUB_XCAS = MKTAG('X','C','A','S'),
|
||||
SUB_XCMO = MKTAG('X','C','M','O'),
|
||||
SUB_XCIM = MKTAG('X','C','I','M'),
|
||||
SUB_XCMT = MKTAG('X','C','M','T'), // TES4 only?
|
||||
SUB_XRNK = MKTAG('X','R','N','K'), // TES4 only?
|
||||
SUB_XGLB = MKTAG('X','G','L','B'), // TES4 only?
|
||||
|
||||
SUB_VNML = MKTAG('V','N','M','L'),
|
||||
SUB_VHGT = MKTAG('V','H','G','T'),
|
||||
SUB_VCLR = MKTAG('V','C','L','R'),
|
||||
SUA_BTXT = MKTAG('B','T','X','T'),
|
||||
SUB_ATXT = MKTAG('A','T','X','T'),
|
||||
SUB_VTXT = MKTAG('V','T','X','T'),
|
||||
SUB_VTEX = MKTAG('V','T','E','X'),
|
||||
|
||||
SUB_HNAM = MKTAG('H','N','A','M'),
|
||||
SUB_GNAM = MKTAG('G','N','A','M'),
|
||||
|
||||
SUB_RCLR = MKTAG('R','C','L','R'),
|
||||
SUB_RPLI = MKTAG('R','P','L','I'),
|
||||
SUB_RPLD = MKTAG('R','P','L','D'),
|
||||
SUB_RDAT = MKTAG('R','D','A','T'),
|
||||
SUB_RDMD = MKTAG('R','D','M','D'), // TES4 only?
|
||||
SUB_RDSD = MKTAG('R','D','S','D'), // TES4 only?
|
||||
SUB_RDGS = MKTAG('R','D','G','S'), // TES4 only?
|
||||
SUB_RDMO = MKTAG('R','D','M','O'),
|
||||
SUB_RDSA = MKTAG('R','D','S','A'),
|
||||
SUB_RDWT = MKTAG('R','D','W','T'),
|
||||
SUB_RDOT = MKTAG('R','D','O','T'),
|
||||
SUB_RDMP = MKTAG('R','D','M','P'),
|
||||
|
||||
SUB_MODB = MKTAG('M','O','D','B'),
|
||||
SUB_OBND = MKTAG('O','B','N','D'),
|
||||
SUB_MODS = MKTAG('M','O','D','S'),
|
||||
|
||||
SUB_NAME = MKTAG('N','A','M','E'),
|
||||
SUB_XMRK = MKTAG('X','M','R','K'),
|
||||
SUB_FNAM = MKTAG('F','N','A','M'),
|
||||
SUB_XSCL = MKTAG('X','S','C','L'),
|
||||
SUB_XTEL = MKTAG('X','T','E','L'),
|
||||
SUB_XTRG = MKTAG('X','T','R','G'),
|
||||
SUB_XSED = MKTAG('X','S','E','D'),
|
||||
SUB_XLOD = MKTAG('X','L','O','D'),
|
||||
SUB_XPCI = MKTAG('X','P','C','I'),
|
||||
SUB_XLOC = MKTAG('X','L','O','C'),
|
||||
SUB_XESP = MKTAG('X','E','S','P'),
|
||||
SUB_XLCM = MKTAG('X','L','C','M'),
|
||||
SUB_XRTM = MKTAG('X','R','T','M'),
|
||||
SUB_XACT = MKTAG('X','A','C','T'),
|
||||
SUB_XCNT = MKTAG('X','C','N','T'),
|
||||
SUB_VMAD = MKTAG('V','M','A','D'),
|
||||
SUB_XPRM = MKTAG('X','P','R','M'),
|
||||
SUB_XMBO = MKTAG('X','M','B','O'),
|
||||
SUB_XPOD = MKTAG('X','P','O','D'),
|
||||
SUB_XRMR = MKTAG('X','R','M','R'),
|
||||
SUB_INAM = MKTAG('I','N','A','M'),
|
||||
SUB_SCHR = MKTAG('S','C','H','R'),
|
||||
SUB_XLRM = MKTAG('X','L','R','M'),
|
||||
SUB_XRGD = MKTAG('X','R','G','D'),
|
||||
SUB_XRDS = MKTAG('X','R','D','S'),
|
||||
SUB_XEMI = MKTAG('X','E','M','I'),
|
||||
SUB_XLIG = MKTAG('X','L','I','G'),
|
||||
SUB_XALP = MKTAG('X','A','L','P'),
|
||||
SUB_XNDP = MKTAG('X','N','D','P'),
|
||||
SUB_XAPD = MKTAG('X','A','P','D'),
|
||||
SUB_XAPR = MKTAG('X','A','P','R'),
|
||||
SUB_XLIB = MKTAG('X','L','I','B'),
|
||||
SUB_XLKR = MKTAG('X','L','K','R'),
|
||||
SUB_XLRT = MKTAG('X','L','R','T'),
|
||||
SUB_XCVL = MKTAG('X','C','V','L'),
|
||||
SUB_XCVR = MKTAG('X','C','V','R'),
|
||||
SUB_XCZA = MKTAG('X','C','Z','A'),
|
||||
SUB_XCZC = MKTAG('X','C','Z','C'),
|
||||
SUB_XFVC = MKTAG('X','F','V','C'),
|
||||
SUB_XHTW = MKTAG('X','H','T','W'),
|
||||
SUB_XIS2 = MKTAG('X','I','S','2'),
|
||||
SUB_XMBR = MKTAG('X','M','B','R'),
|
||||
SUB_XCCP = MKTAG('X','C','C','P'),
|
||||
SUB_XPWR = MKTAG('X','P','W','R'),
|
||||
SUB_XTRI = MKTAG('X','T','R','I'),
|
||||
SUB_XATR = MKTAG('X','A','T','R'),
|
||||
SUB_XPRD = MKTAG('X','P','R','D'),
|
||||
SUB_XPPA = MKTAG('X','P','P','A'),
|
||||
SUB_PDTO = MKTAG('P','D','T','O'),
|
||||
SUB_XLRL = MKTAG('X','L','R','L'),
|
||||
|
||||
SUB_QNAM = MKTAG('Q','N','A','M'),
|
||||
SUB_COCT = MKTAG('C','O','C','T'),
|
||||
SUB_COED = MKTAG('C','O','E','D'),
|
||||
SUB_CNTO = MKTAG('C','N','T','O'),
|
||||
SUB_SCRI = MKTAG('S','C','R','I'),
|
||||
|
||||
SUB_BNAM = MKTAG('B','N','A','M'),
|
||||
|
||||
SUB_BMDT = MKTAG('B','M','D','T'),
|
||||
SUB_MOD2 = MKTAG('M','O','D','2'),
|
||||
SUB_MOD3 = MKTAG('M','O','D','3'),
|
||||
SUB_MOD4 = MKTAG('M','O','D','4'),
|
||||
SUB_MO2B = MKTAG('M','O','2','B'),
|
||||
SUB_MO3B = MKTAG('M','O','3','B'),
|
||||
SUB_MO4B = MKTAG('M','O','4','B'),
|
||||
SUB_MO2T = MKTAG('M','O','2','T'),
|
||||
SUB_MO3T = MKTAG('M','O','3','T'),
|
||||
SUB_MO4T = MKTAG('M','O','4','T'),
|
||||
SUB_ANAM = MKTAG('A','N','A','M'),
|
||||
SUB_ENAM = MKTAG('E','N','A','M'),
|
||||
SUB_ICO2 = MKTAG('I','C','O','2'),
|
||||
|
||||
SUB_ACBS = MKTAG('A','C','B','S'),
|
||||
SUB_SPLO = MKTAG('S','P','L','O'),
|
||||
SUB_AIDT = MKTAG('A','I','D','T'),
|
||||
SUB_PKID = MKTAG('P','K','I','D'),
|
||||
SUB_HCLR = MKTAG('H','C','L','R'),
|
||||
SUB_FGGS = MKTAG('F','G','G','S'),
|
||||
SUB_FGGA = MKTAG('F','G','G','A'),
|
||||
SUB_FGTS = MKTAG('F','G','T','S'),
|
||||
SUB_KFFZ = MKTAG('K','F','F','Z'),
|
||||
|
||||
SUB_PFIG = MKTAG('P','F','I','G'),
|
||||
SUB_PFPC = MKTAG('P','F','P','C'),
|
||||
|
||||
SUB_XHRS = MKTAG('X','H','R','S'),
|
||||
SUB_XMRC = MKTAG('X','M','R','C'),
|
||||
|
||||
SUB_SNDD = MKTAG('S','N','D','D'),
|
||||
SUB_SNDX = MKTAG('S','N','D','X'),
|
||||
|
||||
SUB_DESC = MKTAG('D','E','S','C'),
|
||||
|
||||
SUB_ENIT = MKTAG('E','N','I','T'),
|
||||
SUB_EFID = MKTAG('E','F','I','D'),
|
||||
SUB_EFIT = MKTAG('E','F','I','T'),
|
||||
SUB_SCIT = MKTAG('S','C','I','T'),
|
||||
|
||||
SUB_SOUL = MKTAG('S','O','U','L'),
|
||||
SUB_SLCP = MKTAG('S','L','C','P'),
|
||||
|
||||
SUB_CSCR = MKTAG('C','S','C','R'),
|
||||
SUB_CSDI = MKTAG('C','S','D','I'),
|
||||
SUB_CSDC = MKTAG('C','S','D','C'),
|
||||
SUB_NIFZ = MKTAG('N','I','F','Z'),
|
||||
SUB_CSDT = MKTAG('C','S','D','T'),
|
||||
SUB_NAM1 = MKTAG('N','A','M','1'),
|
||||
SUB_NIFT = MKTAG('N','I','F','T'),
|
||||
|
||||
SUB_LVLD = MKTAG('L','V','L','D'),
|
||||
SUB_LVLF = MKTAG('L','V','L','F'),
|
||||
SUB_LVLO = MKTAG('L','V','L','O'),
|
||||
|
||||
SUB_BODT = MKTAG('B','O','D','T'),
|
||||
SUB_YNAM = MKTAG('Y','N','A','M'),
|
||||
SUB_DEST = MKTAG('D','E','S','T'),
|
||||
SUB_DMDL = MKTAG('D','M','D','L'),
|
||||
SUB_DMDS = MKTAG('D','M','D','S'),
|
||||
SUB_DMDT = MKTAG('D','M','D','T'),
|
||||
SUB_DSTD = MKTAG('D','S','T','D'),
|
||||
SUB_DSTF = MKTAG('D','S','T','F'),
|
||||
SUB_KNAM = MKTAG('K','N','A','M'),
|
||||
SUB_KSIZ = MKTAG('K','S','I','Z'),
|
||||
SUB_KWDA = MKTAG('K','W','D','A'),
|
||||
SUB_VNAM = MKTAG('V','N','A','M'),
|
||||
SUB_SDSC = MKTAG('S','D','S','C'),
|
||||
SUB_MO2S = MKTAG('M','O','2','S'),
|
||||
SUB_MO4S = MKTAG('M','O','4','S'),
|
||||
SUB_BOD2 = MKTAG('B','O','D','2'),
|
||||
SUB_BAMT = MKTAG('B','A','M','T'),
|
||||
SUB_BIDS = MKTAG('B','I','D','S'),
|
||||
SUB_ETYP = MKTAG('E','T','Y','P'),
|
||||
SUB_BMCT = MKTAG('B','M','C','T'),
|
||||
SUB_MICO = MKTAG('M','I','C','O'),
|
||||
SUB_MIC2 = MKTAG('M','I','C','2'),
|
||||
SUB_EAMT = MKTAG('E','A','M','T'),
|
||||
SUB_EITM = MKTAG('E','I','T','M'),
|
||||
|
||||
SUB_SCTX = MKTAG('S','C','T','X'),
|
||||
SUB_XLTW = MKTAG('X','L','T','W'),
|
||||
SUB_XMBP = MKTAG('X','M','B','P'),
|
||||
SUB_XOCP = MKTAG('X','O','C','P'),
|
||||
SUB_XRGB = MKTAG('X','R','G','B'),
|
||||
SUB_XSPC = MKTAG('X','S','P','C'),
|
||||
SUB_XTNM = MKTAG('X','T','N','M'),
|
||||
SUB_ATKR = MKTAG('A','T','K','R'),
|
||||
SUB_CRIF = MKTAG('C','R','I','F'),
|
||||
SUB_DOFT = MKTAG('D','O','F','T'),
|
||||
SUB_DPLT = MKTAG('D','P','L','T'),
|
||||
SUB_ECOR = MKTAG('E','C','O','R'),
|
||||
SUB_ATKD = MKTAG('A','T','K','D'),
|
||||
SUB_ATKE = MKTAG('A','T','K','E'),
|
||||
SUB_FTST = MKTAG('F','T','S','T'),
|
||||
SUB_HCLF = MKTAG('H','C','L','F'),
|
||||
SUB_NAM5 = MKTAG('N','A','M','5'),
|
||||
SUB_NAM6 = MKTAG('N','A','M','6'),
|
||||
SUB_NAM7 = MKTAG('N','A','M','7'),
|
||||
SUB_NAM8 = MKTAG('N','A','M','8'),
|
||||
SUB_PRKR = MKTAG('P','R','K','R'),
|
||||
SUB_PRKZ = MKTAG('P','R','K','Z'),
|
||||
SUB_SOFT = MKTAG('S','O','F','T'),
|
||||
SUB_SPCT = MKTAG('S','P','C','T'),
|
||||
SUB_TINC = MKTAG('T','I','N','C'),
|
||||
SUB_TIAS = MKTAG('T','I','A','S'),
|
||||
SUB_TINI = MKTAG('T','I','N','I'),
|
||||
SUB_TINV = MKTAG('T','I','N','V'),
|
||||
SUB_TPLT = MKTAG('T','P','L','T'),
|
||||
SUB_VTCK = MKTAG('V','T','C','K'),
|
||||
SUB_SHRT = MKTAG('S','H','R','T'),
|
||||
SUB_SPOR = MKTAG('S','P','O','R'),
|
||||
SUB_XHOR = MKTAG('X','H','O','R'),
|
||||
SUB_CTDA = MKTAG('C','T','D','A'),
|
||||
SUB_CRDT = MKTAG('C','R','D','T'),
|
||||
SUB_FNMK = MKTAG('F','N','M','K'),
|
||||
SUB_FNPR = MKTAG('F','N','P','R'),
|
||||
SUB_WBDT = MKTAG('W','B','D','T'),
|
||||
SUB_QUAL = MKTAG('Q','U','A','L'),
|
||||
SUB_INDX = MKTAG('I','N','D','X'),
|
||||
SUB_ATTR = MKTAG('A','T','T','R'),
|
||||
SUB_MTNM = MKTAG('M','T','N','M'),
|
||||
SUB_UNES = MKTAG('U','N','E','S'),
|
||||
SUB_TIND = MKTAG('T','I','N','D'),
|
||||
SUB_TINL = MKTAG('T','I','N','L'),
|
||||
SUB_TINP = MKTAG('T','I','N','P'),
|
||||
SUB_TINT = MKTAG('T','I','N','T'),
|
||||
SUB_TIRS = MKTAG('T','I','R','S'),
|
||||
SUB_PHWT = MKTAG('P','H','W','T'),
|
||||
SUB_AHCF = MKTAG('A','H','C','F'),
|
||||
SUB_AHCM = MKTAG('A','H','C','M'),
|
||||
SUB_HEAD = MKTAG('H','E','A','D'),
|
||||
SUB_MPAI = MKTAG('M','P','A','I'),
|
||||
SUB_MPAV = MKTAG('M','P','A','V'),
|
||||
SUB_DFTF = MKTAG('D','F','T','F'),
|
||||
SUB_DFTM = MKTAG('D','F','T','M'),
|
||||
SUB_FLMV = MKTAG('F','L','M','V'),
|
||||
SUB_FTSF = MKTAG('F','T','S','F'),
|
||||
SUB_FTSM = MKTAG('F','T','S','M'),
|
||||
SUB_MTYP = MKTAG('M','T','Y','P'),
|
||||
SUB_PHTN = MKTAG('P','H','T','N'),
|
||||
SUB_RNMV = MKTAG('R','N','M','V'),
|
||||
SUB_RPRF = MKTAG('R','P','R','F'),
|
||||
SUB_RPRM = MKTAG('R','P','R','M'),
|
||||
SUB_SNMV = MKTAG('S','N','M','V'),
|
||||
SUB_SPED = MKTAG('S','P','E','D'),
|
||||
SUB_SWMV = MKTAG('S','W','M','V'),
|
||||
SUB_WKMV = MKTAG('W','K','M','V'),
|
||||
SUB_LLCT = MKTAG('L','L','C','T'),
|
||||
SUB_IDLF = MKTAG('I','D','L','F'),
|
||||
SUB_IDLA = MKTAG('I','D','L','A'),
|
||||
SUB_IDLC = MKTAG('I','D','L','C'),
|
||||
SUB_IDLT = MKTAG('I','D','L','T'),
|
||||
SUB_DODT = MKTAG('D','O','D','T'),
|
||||
SUB_TX00 = MKTAG('T','X','0','0'),
|
||||
SUB_TX01 = MKTAG('T','X','0','1'),
|
||||
SUB_TX02 = MKTAG('T','X','0','2'),
|
||||
SUB_TX03 = MKTAG('T','X','0','3'),
|
||||
SUB_TX04 = MKTAG('T','X','0','4'),
|
||||
SUB_TX05 = MKTAG('T','X','0','5'),
|
||||
SUB_TX06 = MKTAG('T','X','0','6'),
|
||||
SUB_TX07 = MKTAG('T','X','0','7'),
|
||||
SUB_BPND = MKTAG('B','P','N','D'),
|
||||
SUB_BPTN = MKTAG('B','P','T','N'),
|
||||
SUB_BPNN = MKTAG('B','P','N','N'),
|
||||
SUB_BPNT = MKTAG('B','P','N','T'),
|
||||
SUB_BPNI = MKTAG('B','P','N','I'),
|
||||
SUB_RAGA = MKTAG('R','A','G','A'),
|
||||
|
||||
SUB_QSTI = MKTAG('Q','S','T','I'),
|
||||
SUB_QSTR = MKTAG('Q','S','T','R'),
|
||||
SUB_QSDT = MKTAG('Q','S','D','T'),
|
||||
SUB_SCDA = MKTAG('S','C','D','A'),
|
||||
SUB_SCRO = MKTAG('S','C','R','O'),
|
||||
SUB_QSTA = MKTAG('Q','S','T','A'),
|
||||
SUB_CTDT = MKTAG('C','T','D','T'),
|
||||
SUB_SCHD = MKTAG('S','C','H','D'),
|
||||
SUB_TCLF = MKTAG('T','C','L','F'),
|
||||
SUB_TCLT = MKTAG('T','C','L','T'),
|
||||
SUB_TRDT = MKTAG('T','R','D','T'),
|
||||
SUB_TPIC = MKTAG('T','P','I','C'),
|
||||
|
||||
SUB_PKDT = MKTAG('P','K','D','T'),
|
||||
SUB_PSDT = MKTAG('P','S','D','T'),
|
||||
SUB_PLDT = MKTAG('P','L','D','T'),
|
||||
SUB_PTDT = MKTAG('P','T','D','T'),
|
||||
SUB_PGRP = MKTAG('P','G','R','P'),
|
||||
SUB_PGRR = MKTAG('P','G','R','R'),
|
||||
SUB_PGRI = MKTAG('P','G','R','I'),
|
||||
SUB_PGRL = MKTAG('P','G','R','L'),
|
||||
SUB_PGAG = MKTAG('P','G','A','G'),
|
||||
SUB_FLTV = MKTAG('F','L','T','V'),
|
||||
|
||||
SUB_XHLT = MKTAG('X','H','L','T'), // Unofficial Oblivion Patch
|
||||
SUB_XCHG = MKTAG('X','C','H','G'), // thievery.exp
|
||||
|
||||
SUB_ITXT = MKTAG('I','T','X','T'),
|
||||
SUB_MO5T = MKTAG('M','O','5','T'),
|
||||
SUB_MOD5 = MKTAG('M','O','D','5'),
|
||||
SUB_MDOB = MKTAG('M','D','O','B'),
|
||||
SUB_SPIT = MKTAG('S','P','I','T'),
|
||||
SUB_PTDA = MKTAG('P','T','D','A'), // TES5
|
||||
SUB_PFOR = MKTAG('P','F','O','R'), // TES5
|
||||
SUB_PFO2 = MKTAG('P','F','O','2'), // TES5
|
||||
SUB_PRCB = MKTAG('P','R','C','B'), // TES5
|
||||
SUB_PKCU = MKTAG('P','K','C','U'), // TES5
|
||||
SUB_PKC2 = MKTAG('P','K','C','2'), // TES5
|
||||
SUB_CITC = MKTAG('C','I','T','C'), // TES5
|
||||
SUB_CIS1 = MKTAG('C','I','S','1'), // TES5
|
||||
SUB_CIS2 = MKTAG('C','I','S','2'), // TES5
|
||||
SUB_TIFC = MKTAG('T','I','F','C'), // TES5
|
||||
SUB_ALCA = MKTAG('A','L','C','A'), // TES5
|
||||
SUB_ALCL = MKTAG('A','L','C','L'), // TES5
|
||||
SUB_ALCO = MKTAG('A','L','C','O'), // TES5
|
||||
SUB_ALDN = MKTAG('A','L','D','N'), // TES5
|
||||
SUB_ALEA = MKTAG('A','L','E','A'), // TES5
|
||||
SUB_ALED = MKTAG('A','L','E','D'), // TES5
|
||||
SUB_ALEQ = MKTAG('A','L','E','Q'), // TES5
|
||||
SUB_ALFA = MKTAG('A','L','F','A'), // TES5
|
||||
SUB_ALFC = MKTAG('A','L','F','C'), // TES5
|
||||
SUB_ALFD = MKTAG('A','L','F','D'), // TES5
|
||||
SUB_ALFE = MKTAG('A','L','F','E'), // TES5
|
||||
SUB_ALFI = MKTAG('A','L','F','I'), // TES5
|
||||
SUB_ALFL = MKTAG('A','L','F','L'), // TES5
|
||||
SUB_ALFR = MKTAG('A','L','F','R'), // TES5
|
||||
SUB_ALID = MKTAG('A','L','I','D'), // TES5
|
||||
SUB_ALLS = MKTAG('A','L','L','S'), // TES5
|
||||
SUB_ALNA = MKTAG('A','L','N','A'), // TES5
|
||||
SUB_ALNT = MKTAG('A','L','N','T'), // TES5
|
||||
SUB_ALPC = MKTAG('A','L','P','C'), // TES5
|
||||
SUB_ALRT = MKTAG('A','L','R','T'), // TES5
|
||||
SUB_ALSP = MKTAG('A','L','S','P'), // TES5
|
||||
SUB_ALST = MKTAG('A','L','S','T'), // TES5
|
||||
SUB_ALUA = MKTAG('A','L','U','A'), // TES5
|
||||
SUB_FLTR = MKTAG('F','L','T','R'), // TES5
|
||||
SUB_QTGL = MKTAG('Q','T','G','L'), // TES5
|
||||
SUB_TWAT = MKTAG('T','W','A','T'), // TES5
|
||||
SUB_XIBS = MKTAG('X','I','B','S'), // FO3
|
||||
SUB_REPL = MKTAG('R','E','P','L'), // FO3
|
||||
SUB_BIPL = MKTAG('B','I','P','L'), // FO3
|
||||
SUB_MODD = MKTAG('M','O','D','D'), // FO3
|
||||
SUB_MOSD = MKTAG('M','O','S','D'), // FO3
|
||||
SUB_MO3S = MKTAG('M','O','3','S'), // FO3
|
||||
SUB_XCET = MKTAG('X','C','E','T'), // FO3
|
||||
SUB_LVLG = MKTAG('L','V','L','G'), // FO3
|
||||
SUB_NVCI = MKTAG('N','V','C','I'), // FO3
|
||||
SUB_NVVX = MKTAG('N','V','V','X'), // FO3
|
||||
SUB_NVTR = MKTAG('N','V','T','R'), // FO3
|
||||
SUB_NVCA = MKTAG('N','V','C','A'), // FO3
|
||||
SUB_NVDP = MKTAG('N','V','D','P'), // FO3
|
||||
SUB_NVGD = MKTAG('N','V','G','D'), // FO3
|
||||
SUB_NVEX = MKTAG('N','V','E','X'), // FO3
|
||||
SUB_XHLP = MKTAG('X','H','L','P'), // FO3
|
||||
SUB_XRDO = MKTAG('X','R','D','O'), // FO3
|
||||
SUB_XAMT = MKTAG('X','A','M','T'), // FO3
|
||||
SUB_XAMC = MKTAG('X','A','M','C'), // FO3
|
||||
SUB_XRAD = MKTAG('X','R','A','D'), // FO3
|
||||
SUB_XORD = MKTAG('X','O','R','D'), // FO3
|
||||
SUB_XCLP = MKTAG('X','C','L','P'), // FO3
|
||||
SUB_NEXT = MKTAG('N','E','X','T'), // FO3
|
||||
SUB_QOBJ = MKTAG('Q','O','B','J'), // FO3
|
||||
SUB_POBA = MKTAG('P','O','B','A'), // FO3
|
||||
SUB_POCA = MKTAG('P','O','C','A'), // FO3
|
||||
SUB_POEA = MKTAG('P','O','E','A'), // FO3
|
||||
SUB_PKDD = MKTAG('P','K','D','D'), // FO3
|
||||
SUB_PKD2 = MKTAG('P','K','D','2'), // FO3
|
||||
SUB_PKPT = MKTAG('P','K','P','T'), // FO3
|
||||
SUB_PKED = MKTAG('P','K','E','D'), // FO3
|
||||
SUB_PKE2 = MKTAG('P','K','E','2'), // FO3
|
||||
SUB_PKAM = MKTAG('P','K','A','M'), // FO3
|
||||
SUB_PUID = MKTAG('P','U','I','D'), // FO3
|
||||
SUB_PKW3 = MKTAG('P','K','W','3'), // FO3
|
||||
SUB_PTD2 = MKTAG('P','T','D','2'), // FO3
|
||||
SUB_PLD2 = MKTAG('P','L','D','2'), // FO3
|
||||
SUB_PKFD = MKTAG('P','K','F','D'), // FO3
|
||||
SUB_IDLB = MKTAG('I','D','L','B'), // FO3
|
||||
SUB_XDCR = MKTAG('X','D','C','R'), // FO3
|
||||
SUB_DALC = MKTAG('D','A','L','C'), // FO3
|
||||
SUB_IMPS = MKTAG('I','M','P','S'), // FO3 Anchorage
|
||||
SUB_IMPF = MKTAG('I','M','P','F'), // FO3 Anchorage
|
||||
|
||||
SUB_XATO = MKTAG('X','A','T','O'), // FONV
|
||||
SUB_INFC = MKTAG('I','N','F','C'), // FONV
|
||||
SUB_INFX = MKTAG('I','N','F','X'), // FONV
|
||||
SUB_TDUM = MKTAG('T','D','U','M'), // FONV
|
||||
SUB_TCFU = MKTAG('T','C','F','U'), // FONV
|
||||
SUB_DAT2 = MKTAG('D','A','T','2'), // FONV
|
||||
SUB_RCIL = MKTAG('R','C','I','L'), // FONV
|
||||
SUB_MMRK = MKTAG('M','M','R','K'), // FONV
|
||||
SUB_SCRV = MKTAG('S','C','R','V'), // FONV
|
||||
SUB_SCVR = MKTAG('S','C','V','R'), // FONV
|
||||
SUB_SLSD = MKTAG('S','L','S','D'), // FONV
|
||||
SUB_XSRF = MKTAG('X','S','R','F'), // FONV
|
||||
SUB_XSRD = MKTAG('X','S','R','D'), // FONV
|
||||
SUB_WMI1 = MKTAG('W','M','I','1'), // FONV
|
||||
SUB_RDID = MKTAG('R','D','I','D'), // FONV
|
||||
SUB_RDSB = MKTAG('R','D','S','B'), // FONV
|
||||
SUB_RDSI = MKTAG('R','D','S','I'), // FONV
|
||||
SUB_BRUS = MKTAG('B','R','U','S'), // FONV
|
||||
SUB_VATS = MKTAG('V','A','T','S'), // FONV
|
||||
SUB_VANM = MKTAG('V','A','N','M'), // FONV
|
||||
SUB_MWD1 = MKTAG('M','W','D','1'), // FONV
|
||||
SUB_MWD2 = MKTAG('M','W','D','2'), // FONV
|
||||
SUB_MWD3 = MKTAG('M','W','D','3'), // FONV
|
||||
SUB_MWD4 = MKTAG('M','W','D','4'), // FONV
|
||||
SUB_MWD5 = MKTAG('M','W','D','5'), // FONV
|
||||
SUB_MWD6 = MKTAG('M','W','D','6'), // FONV
|
||||
SUB_MWD7 = MKTAG('M','W','D','7'), // FONV
|
||||
SUB_WMI2 = MKTAG('W','M','I','2'), // FONV
|
||||
SUB_WMI3 = MKTAG('W','M','I','3'), // FONV
|
||||
SUB_WMS1 = MKTAG('W','M','S','1'), // FONV
|
||||
SUB_WMS2 = MKTAG('W','M','S','2'), // FONV
|
||||
SUB_WNM1 = MKTAG('W','N','M','1'), // FONV
|
||||
SUB_WNM2 = MKTAG('W','N','M','2'), // FONV
|
||||
SUB_WNM3 = MKTAG('W','N','M','3'), // FONV
|
||||
SUB_WNM4 = MKTAG('W','N','M','4'), // FONV
|
||||
SUB_WNM5 = MKTAG('W','N','M','5'), // FONV
|
||||
SUB_WNM6 = MKTAG('W','N','M','6'), // FONV
|
||||
SUB_WNM7 = MKTAG('W','N','M','7'), // FONV
|
||||
SUB_JNAM = MKTAG('J','N','A','M'), // FONV
|
||||
SUB_EFSD = MKTAG('E','F','S','D'), // FONV DeadMoney
|
||||
};
|
||||
|
||||
enum MagicEffectID
|
||||
{
|
||||
// Alteration
|
||||
EFI_BRDN = MKTAG('B','R','D','N'),
|
||||
EFI_FTHR = MKTAG('F','T','H','R'),
|
||||
EFI_FISH = MKTAG('F','I','S','H'),
|
||||
EFI_FRSH = MKTAG('F','R','S','H'),
|
||||
EFI_OPEN = MKTAG('O','P','N','N'),
|
||||
EFI_SHLD = MKTAG('S','H','L','D'),
|
||||
EFI_LISH = MKTAG('L','I','S','H'),
|
||||
EFI_WABR = MKTAG('W','A','B','R'),
|
||||
EFI_WAWA = MKTAG('W','A','W','A'),
|
||||
|
||||
// Conjuration
|
||||
EFI_BABO = MKTAG('B','A','B','O'), // Bound Boots
|
||||
EFI_BACU = MKTAG('B','A','C','U'), // Bound Cuirass
|
||||
EFI_BAGA = MKTAG('B','A','G','A'), // Bound Gauntlets
|
||||
EFI_BAGR = MKTAG('B','A','G','R'), // Bound Greaves
|
||||
EFI_BAHE = MKTAG('B','A','H','E'), // Bound Helmet
|
||||
EFI_BASH = MKTAG('B','A','S','H'), // Bound Shield
|
||||
EFI_BWAX = MKTAG('B','W','A','X'), // Bound Axe
|
||||
EFI_BWBO = MKTAG('B','W','B','O'), // Bound Bow
|
||||
EFI_BWDA = MKTAG('B','W','D','A'), // Bound Dagger
|
||||
EFI_BWMA = MKTAG('B','W','M','A'), // Bound Mace
|
||||
EFI_BWSW = MKTAG('B','W','S','W'), // Bound Sword
|
||||
EFI_Z001 = MKTAG('Z','0','0','1'), // Summon Rufio's Ghost
|
||||
EFI_Z002 = MKTAG('Z','0','0','2'), // Summon Ancestor Guardian
|
||||
EFI_Z003 = MKTAG('Z','0','0','3'), // Summon Spiderling
|
||||
EFI_Z005 = MKTAG('Z','0','0','5'), // Summon Bear
|
||||
EFI_ZCLA = MKTAG('Z','C','L','A'), // Summon Clannfear
|
||||
EFI_ZDAE = MKTAG('Z','D','A','E'), // Summon Daedroth
|
||||
EFI_ZDRE = MKTAG('Z','D','R','E'), // Summon Dremora
|
||||
EFI_ZDRL = MKTAG('Z','D','R','L'), // Summon Dremora Lord
|
||||
EFI_ZFIA = MKTAG('Z','F','I','A'), // Summon Flame Atronach
|
||||
EFI_ZFRA = MKTAG('Z','F','R','A'), // Summon Frost Atronach
|
||||
EFI_ZGHO = MKTAG('Z','G','H','O'), // Summon Ghost
|
||||
EFI_ZHDZ = MKTAG('Z','H','D','Z'), // Summon Headless Zombie
|
||||
EFI_ZLIC = MKTAG('Z','L','I','C'), // Summon Lich
|
||||
EFI_ZSCA = MKTAG('Z','S','C','A'), // Summon Scamp
|
||||
EFI_ZSKE = MKTAG('Z','S','K','E'), // Summon Skeleton
|
||||
EFI_ZSKA = MKTAG('Z','S','K','A'), // Summon Skeleton Guardian
|
||||
EFI_ZSKH = MKTAG('Z','S','K','H'), // Summon Skeleton Hero
|
||||
EFI_ZSKC = MKTAG('Z','S','K','C'), // Summon Skeleton Champion
|
||||
EFI_ZSPD = MKTAG('Z','S','P','D'), // Summon Spider Daedra
|
||||
EFI_ZSTA = MKTAG('Z','S','T','A'), // Summon Storm Atronach
|
||||
EFI_ZWRA = MKTAG('Z','W','R','A'), // Summon Faded Wraith
|
||||
EFI_ZWRL = MKTAG('Z','W','R','L'), // Summon Gloom Wraith
|
||||
EFI_ZXIV = MKTAG('Z','X','I','V'), // Summon Xivilai
|
||||
EFI_ZZOM = MKTAG('Z','Z','O','M'), // Summon Zombie
|
||||
EFI_TURN = MKTAG('T','U','R','N'), // Turn Undead
|
||||
|
||||
// Destruction
|
||||
EFI_DGAT = MKTAG('D','G','A','T'), // Damage Attribute
|
||||
EFI_DGFA = MKTAG('D','G','F','A'), // Damage Fatigue
|
||||
EFI_DGHE = MKTAG('D','G','H','E'), // Damage Health
|
||||
EFI_DGSP = MKTAG('D','G','S','P'), // Damage Magicka
|
||||
EFI_DIAR = MKTAG('D','I','A','R'), // Disintegrate Armor
|
||||
EFI_DIWE = MKTAG('D','I','W','E'), // Disintegrate Weapon
|
||||
EFI_DRAT = MKTAG('D','R','A','T'), // Drain Attribute
|
||||
EFI_DRFA = MKTAG('D','R','F','A'), // Drain Fatigue
|
||||
EFI_DRHE = MKTAG('D','R','H','E'), // Drain Health
|
||||
EFI_DRSP = MKTAG('D','R','S','P'), // Drain Magicka
|
||||
EFI_DRSK = MKTAG('D','R','S','K'), // Drain Skill
|
||||
EFI_FIDG = MKTAG('F','I','D','G'), // Fire Damage
|
||||
EFI_FRDG = MKTAG('F','R','D','G'), // Frost Damage
|
||||
EFI_SHDG = MKTAG('S','H','D','G'), // Shock Damage
|
||||
EFI_WKDI = MKTAG('W','K','D','I'), // Weakness to Disease
|
||||
EFI_WKFI = MKTAG('W','K','F','I'), // Weakness to Fire
|
||||
EFI_WKFR = MKTAG('W','K','F','R'), // Weakness to Frost
|
||||
EFI_WKMA = MKTAG('W','K','M','A'), // Weakness to Magic
|
||||
EFI_WKNW = MKTAG('W','K','N','W'), // Weakness to Normal Weapons
|
||||
EFI_WKPO = MKTAG('W','K','P','O'), // Weakness to Poison
|
||||
EFI_WKSH = MKTAG('W','K','S','H'), // Weakness to Shock
|
||||
|
||||
// Illusion
|
||||
EFI_CALM = MKTAG('C','A','L','M'), // Calm
|
||||
EFI_CHML = MKTAG('C','H','M','L'), // Chameleon
|
||||
EFI_CHRM = MKTAG('C','H','R','M'), // Charm
|
||||
EFI_COCR = MKTAG('C','O','C','R'), // Command Creature
|
||||
EFI_COHU = MKTAG('C','O','H','U'), // Command Humanoid
|
||||
EFI_DEMO = MKTAG('D','E','M','O'), // Demoralize
|
||||
EFI_FRNZ = MKTAG('F','R','N','Z'), // Frenzy
|
||||
EFI_INVI = MKTAG('I','N','V','I'), // Invisibility
|
||||
EFI_LGHT = MKTAG('L','G','H','T'), // Light
|
||||
EFI_NEYE = MKTAG('N','E','Y','E'), // Night-Eye
|
||||
EFI_PARA = MKTAG('P','A','R','A'), // Paralyze
|
||||
EFI_RALY = MKTAG('R','A','L','Y'), // Rally
|
||||
EFI_SLNC = MKTAG('S','L','N','C'), // Silence
|
||||
|
||||
// Mysticism
|
||||
EFI_DTCT = MKTAG('D','T','C','T'), // Detect Life
|
||||
EFI_DSPL = MKTAG('D','S','P','L'), // Dispel
|
||||
EFI_REDG = MKTAG('R','E','D','G'), // Reflect Damage
|
||||
EFI_RFLC = MKTAG('R','F','L','C'), // Reflect Spell
|
||||
EFI_STRP = MKTAG('S','T','R','P'), // Soul Trap
|
||||
EFI_SABS = MKTAG('S','A','B','S'), // Spell Absorption
|
||||
EFI_TELE = MKTAG('T','E','L','E'), // Telekinesis
|
||||
|
||||
// Restoration
|
||||
EFI_ABAT = MKTAG('A','B','A','T'), // Absorb Attribute
|
||||
EFI_ABFA = MKTAG('A','B','F','A'), // Absorb Fatigue
|
||||
EFI_ABHe = MKTAG('A','B','H','e'), // Absorb Health
|
||||
EFI_ABSP = MKTAG('A','B','S','P'), // Absorb Magicka
|
||||
EFI_ABSK = MKTAG('A','B','S','K'), // Absorb Skill
|
||||
EFI_1400 = MKTAG('1','4','0','0'), // Cure Disease
|
||||
EFI_CUPA = MKTAG('C','U','P','A'), // Cure Paralysis
|
||||
EFI_CUPO = MKTAG('C','U','P','O'), // Cure Poison
|
||||
EFI_FOAT = MKTAG('F','O','A','T'), // Fortify Attribute
|
||||
EFI_FOFA = MKTAG('F','O','F','A'), // Fortify Fatigue
|
||||
EFI_FOHE = MKTAG('F','O','H','E'), // Fortify Health
|
||||
EFI_FOSP = MKTAG('F','O','S','P'), // Fortify Magicka
|
||||
EFI_FOSK = MKTAG('F','O','S','K'), // Fortify Skill
|
||||
EFI_RSDI = MKTAG('R','S','D','I'), // Resist Disease
|
||||
EFI_RSFI = MKTAG('R','S','F','I'), // Resist Fire
|
||||
EFI_RSFR = MKTAG('R','S','F','R'), // Resist Frost
|
||||
EFI_RSMA = MKTAG('R','S','M','A'), // Resist Magic
|
||||
EFI_RSNW = MKTAG('R','S','N','W'), // Resist Normal Weapons
|
||||
EFI_RSPA = MKTAG('R','S','P','A'), // Resist Paralysis
|
||||
EFI_RSPO = MKTAG('R','S','P','O'), // Resist Poison
|
||||
EFI_RSSH = MKTAG('R','S','S','H'), // Resist Shock
|
||||
EFI_REAT = MKTAG('R','E','A','T'), // Restore Attribute
|
||||
EFI_REFA = MKTAG('R','E','F','A'), // Restore Fatigue
|
||||
EFI_REHE = MKTAG('R','E','H','E'), // Restore Health
|
||||
EFI_RESP = MKTAG('R','E','S','P'), // Restore Magicka
|
||||
|
||||
// Effects
|
||||
EFI_LOCK = MKTAG('L','O','C','K'), // Lock Lock
|
||||
EFI_SEFF = MKTAG('S','E','F','F'), // Script Effect
|
||||
EFI_Z020 = MKTAG('Z','0','2','0'), // Summon 20 Extra
|
||||
EFI_MYHL = MKTAG('M','Y','H','L'), // Summon Mythic Dawn Helmet
|
||||
EFI_MYTH = MKTAG('M','Y','T','H'), // Summon Mythic Dawn Armor
|
||||
EFI_REAN = MKTAG('R','E','A','N'), // Reanimate
|
||||
EFI_DISE = MKTAG('D','I','S','E'), // Disease Info
|
||||
EFI_POSN = MKTAG('P','O','S','N'), // Poison Info
|
||||
EFI_DUMY = MKTAG('D','U','M','Y'), // Mehrunes Dagon Custom Effect
|
||||
EFI_STMA = MKTAG('S','T','M','A'), // Stunted Magicka
|
||||
EFI_SUDG = MKTAG('S','U','D','G'), // Sun Damage
|
||||
EFI_VAMP = MKTAG('V','A','M','P'), // Vampirism
|
||||
EFI_DARK = MKTAG('D','A','R','K'), // Darkness
|
||||
EFI_RSWD = MKTAG('R','S','W','D') // Resist Water Damage
|
||||
};
|
||||
|
||||
// Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Groups
|
||||
enum GroupType
|
||||
{
|
||||
Grp_RecordType = 0,
|
||||
Grp_WorldChild = 1,
|
||||
Grp_InteriorCell = 2,
|
||||
Grp_InteriorSubCell = 3,
|
||||
Grp_ExteriorCell = 4,
|
||||
Grp_ExteriorSubCell = 5,
|
||||
Grp_CellChild = 6,
|
||||
Grp_TopicChild = 7,
|
||||
Grp_CellPersistentChild = 8,
|
||||
Grp_CellTemporaryChild = 9,
|
||||
Grp_CellVisibleDistChild = 10
|
||||
};
|
||||
|
||||
// Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records
|
||||
enum RecordFlag
|
||||
{
|
||||
Rec_ESM = 0x00000001, // (TES4 record only) Master (ESM) file.
|
||||
Rec_Deleted = 0x00000020, // Deleted
|
||||
Rec_Constant = 0x00000040, // Constant
|
||||
Rec_HiddenLMap = 0x00000040, // (REFR) Hidden From Local Map (Needs Confirmation: Related to shields)
|
||||
Rec_Localized = 0x00000080, // (TES4 record only) Is localized. This will make Skyrim load the
|
||||
// .STRINGS, .DLSTRINGS, and .ILSTRINGS files associated with the mod.
|
||||
// If this flag is not set, lstrings are treated as zstrings.
|
||||
Rec_FireOff = 0x00000080, // (PHZD) Turn off fire
|
||||
Rec_UpdateAnim = 0x00000100, // Must Update Anims
|
||||
Rec_NoAccess = 0x00000100, // (REFR) Inaccessible
|
||||
Rec_Hidden = 0x00000200, // (REFR) Hidden from local map
|
||||
Rec_StartDead = 0x00000200, // (ACHR) Starts dead /(REFR) MotionBlurCastsShadows
|
||||
Rec_Persistent = 0x00000400, // Quest item / Persistent reference
|
||||
Rec_DispMenu = 0x00000400, // (LSCR) Displays in Main Menu
|
||||
Rec_Disabled = 0x00000800, // Initially disabled
|
||||
Rec_Ignored = 0x00001000, // Ignored
|
||||
Rec_VisDistant = 0x00008000, // Visible when distant
|
||||
Rec_RandAnim = 0x00010000, // (ACTI) Random Animation Start
|
||||
Rec_Danger = 0x00020000, // (ACTI) Dangerous / Off limits (Interior cell)
|
||||
// Dangerous Can't be set withough Ignore Object Interaction
|
||||
Rec_Compressed = 0x00040000, // Data is compressed
|
||||
Rec_CanNotWait = 0x00080000, // Can't wait
|
||||
Rec_IgnoreObj = 0x00100000, // (ACTI) Ignore Object Interaction
|
||||
// Ignore Object Interaction Sets Dangerous Automatically
|
||||
Rec_Marker = 0x00800000, // Is Marker
|
||||
Rec_Obstacle = 0x02000000, // (ACTI) Obstacle / (REFR) No AI Acquire
|
||||
Rec_NavMFilter = 0x04000000, // NavMesh Gen - Filter
|
||||
Rec_NavMBBox = 0x08000000, // NavMesh Gen - Bounding Box
|
||||
Rec_ExitToTalk = 0x10000000, // (FURN) Must Exit to Talk
|
||||
Rec_Refected = 0x10000000, // (REFR) Reflected By Auto Water
|
||||
Rec_ChildUse = 0x20000000, // (FURN/IDLM) Child Can Use
|
||||
Rec_NoHavok = 0x20000000, // (REFR) Don't Havok Settle
|
||||
Rec_NavMGround = 0x40000000, // NavMesh Gen - Ground
|
||||
Rec_NoRespawn = 0x40000000, // (REFR) NoRespawn
|
||||
Rec_MultiBound = 0x80000000 // (REFR) MultiBound
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// NOTE: the label field of a group is not reliable (http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format)
|
||||
union GroupLabel
|
||||
{
|
||||
std::uint32_t value; // formId, blockNo or raw int representation of type
|
||||
char recordType[4]; // record type in ascii
|
||||
std::int16_t grid[2]; // grid y, x (note the reverse order)
|
||||
};
|
||||
|
||||
struct GroupTypeHeader
|
||||
{
|
||||
std::uint32_t typeId;
|
||||
std::uint32_t groupSize; // includes the 24 bytes (20 for TES4) of header (i.e. this struct)
|
||||
GroupLabel label; // format based on type
|
||||
std::int32_t type;
|
||||
std::uint16_t stamp; // & 0xff for day, & 0xff00 for months since Dec 2002 (i.e. 1 = Jan 2003)
|
||||
std::uint16_t unknown;
|
||||
std::uint16_t version; // not in TES4
|
||||
std::uint16_t unknown2; // not in TES4
|
||||
};
|
||||
|
||||
struct RecordTypeHeader
|
||||
{
|
||||
std::uint32_t typeId;
|
||||
std::uint32_t dataSize; // does *not* include 24 bytes (20 for TES4) of header
|
||||
std::uint32_t flags;
|
||||
FormId id;
|
||||
std::uint32_t revision;
|
||||
std::uint16_t version; // not in TES4
|
||||
std::uint16_t unknown; // not in TES4
|
||||
};
|
||||
|
||||
union RecordHeader
|
||||
{
|
||||
struct GroupTypeHeader group;
|
||||
struct RecordTypeHeader record;
|
||||
};
|
||||
|
||||
struct SubRecordHeader
|
||||
{
|
||||
std::uint32_t typeId;
|
||||
std::uint16_t dataSize;
|
||||
};
|
||||
|
||||
// Grid, CellGrid and Vertex are shared by NVMI(NAVI) and NVNM(NAVM)
|
||||
|
||||
struct Grid
|
||||
{
|
||||
std::int16_t x;
|
||||
std::int16_t y;
|
||||
};
|
||||
|
||||
union CellGrid
|
||||
{
|
||||
FormId cellId;
|
||||
Grid grid;
|
||||
};
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// For pretty printing GroupHeader labels
|
||||
std::string printLabel(const GroupLabel& label, const std::uint32_t type);
|
||||
|
||||
void gridToString(std::int16_t x, std::int16_t y, std::string& str);
|
||||
}
|
||||
|
||||
#endif // ESM4_COMMON_H
|
46
components/esm4/dialogue.hpp
Normal file
46
components/esm4/dialogue.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright (C) 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_DIALOGUE_H
|
||||
#define ESM4_DIALOGUE_H
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
enum DialType
|
||||
{
|
||||
DTYP_Topic = 0,
|
||||
DTYP_Conversation = 1,
|
||||
DTYP_Combat = 2,
|
||||
DTYP_Persuation = 3,
|
||||
DTYP_Detection = 4,
|
||||
DTYP_Service = 5,
|
||||
DTYP_Miscellaneous = 6,
|
||||
// below FO3/FONV
|
||||
DTYP_Radio = 7
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_DIALOGUE_H
|
56
components/esm4/effect.hpp
Normal file
56
components/esm4/effect.hpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright (C) 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_EFFECT_H
|
||||
#define ESM4_EFFECT_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
union EFI_Label
|
||||
{
|
||||
std::uint32_t value;
|
||||
char effect[4];
|
||||
};
|
||||
|
||||
struct ScriptEffect
|
||||
{
|
||||
FormId formId; // Script effect (Magic effect must be SEFF)
|
||||
std::int32_t school; // Magic school. See Magic schools for more information.
|
||||
EFI_Label visualEffect; // Visual effect name or 0x00000000 if None
|
||||
std::uint8_t flags; // 0x01 = Hostile
|
||||
std::uint8_t unknown1;
|
||||
std::uint8_t unknown2;
|
||||
std::uint8_t unknown3;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
#endif // ESM4_EFFECT_H
|
78
components/esm4/formid.cpp
Normal file
78
components/esm4/formid.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2020-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
*/
|
||||
#include "formid.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <cstdlib> // strtol
|
||||
#include <climits> // LONG_MIN, LONG_MAX for gcc
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
void formIdToString(FormId formId, std::string& str)
|
||||
{
|
||||
char buf[8+1];
|
||||
int res = snprintf(buf, 8+1, "%08X", formId);
|
||||
if (res > 0 && res < 8+1)
|
||||
str.assign(buf);
|
||||
else
|
||||
throw std::runtime_error("Possible buffer overflow while converting formId");
|
||||
}
|
||||
|
||||
std::string formIdToString(FormId formId)
|
||||
{
|
||||
std::string str;
|
||||
formIdToString(formId, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
bool isFormId(const std::string& str, FormId *id)
|
||||
{
|
||||
if (str.size() != 8)
|
||||
return false;
|
||||
|
||||
char *tmp;
|
||||
errno = 0;
|
||||
unsigned long val = strtol(str.c_str(), &tmp, 16);
|
||||
|
||||
if (tmp == str.c_str() || *tmp != '\0'
|
||||
|| ((val == (unsigned long)LONG_MIN || val == (unsigned long)LONG_MAX) && errno == ERANGE))
|
||||
return false;
|
||||
|
||||
if (id != nullptr)
|
||||
*id = static_cast<FormId>(val);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FormId stringToFormId(const std::string& str)
|
||||
{
|
||||
if (str.size() != 8)
|
||||
throw std::out_of_range("StringToFormId: incorrect string size");
|
||||
|
||||
return static_cast<FormId>(std::stoul(str, nullptr, 16));
|
||||
}
|
||||
}
|
42
components/esm4/formid.hpp
Normal file
42
components/esm4/formid.hpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright (C) 2016 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
*/
|
||||
#ifndef ESM4_FORMID_H
|
||||
#define ESM4_FORMID_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
typedef std::uint32_t FormId;
|
||||
|
||||
void formIdToString(FormId formId, std::string& str);
|
||||
|
||||
std::string formIdToString(FormId formId);
|
||||
|
||||
bool isFormId(const std::string& str, FormId *id = nullptr);
|
||||
|
||||
FormId stringToFormId(const std::string& str);
|
||||
}
|
||||
|
||||
#endif // ESM4_FORMID_H
|
55
components/esm4/inventory.hpp
Normal file
55
components/esm4/inventory.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright (C) 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_INVENTORY_H
|
||||
#define ESM4_INVENTORY_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
// LVLC, LVLI
|
||||
struct LVLO
|
||||
{
|
||||
std::int16_t level;
|
||||
std::uint16_t unknown; // sometimes missing
|
||||
FormId item;
|
||||
std::int16_t count;
|
||||
std::uint16_t unknown2; // sometimes missing
|
||||
};
|
||||
|
||||
struct InventoryItem // NPC_, CREA, CONT
|
||||
{
|
||||
FormId item;
|
||||
std::uint32_t count;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
#endif // ESM4_INVENTORY_H
|
79
components/esm4/lighting.hpp
Normal file
79
components/esm4/lighting.hpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright (C) 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_LIGHTING_H
|
||||
#define ESM4_LIGHTING_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
// guesses only for TES4
|
||||
struct Lighting
|
||||
{ // | Aichan Prison values
|
||||
std::uint32_t ambient; // | 16 17 19 00 (RGBA)
|
||||
std::uint32_t directional; // | 00 00 00 00 (RGBA)
|
||||
std::uint32_t fogColor; // | 1D 1B 16 00 (RGBA)
|
||||
float fogNear; // Fog Near | 00 00 00 00 = 0.f
|
||||
float fogFar; // Fog Far | 00 80 3B 45 = 3000.f
|
||||
std::int32_t rotationXY; // rotation xy | 00 00 00 00 = 0
|
||||
std::int32_t rotationZ; // rotation z | 00 00 00 00 = 0
|
||||
float fogDirFade; // Fog dir fade | 00 00 80 3F = 1.f
|
||||
float fogClipDist; // Fog clip dist | 00 80 3B 45 = 3000.f
|
||||
float fogPower;
|
||||
};
|
||||
|
||||
struct Lighting_TES5
|
||||
{
|
||||
std::uint32_t ambient;
|
||||
std::uint32_t directional;
|
||||
std::uint32_t fogColor;
|
||||
float fogNear;
|
||||
float fogFar;
|
||||
std::int32_t rotationXY;
|
||||
std::int32_t rotationZ;
|
||||
float fogDirFade;
|
||||
float fogClipDist;
|
||||
float fogPower;
|
||||
std::uint32_t unknown1;
|
||||
std::uint32_t unknown2;
|
||||
std::uint32_t unknown3;
|
||||
std::uint32_t unknown4;
|
||||
std::uint32_t unknown5;
|
||||
std::uint32_t unknown6;
|
||||
std::uint32_t unknown7;
|
||||
std::uint32_t unknown8;
|
||||
std::uint32_t fogColorFar;
|
||||
float fogMax;
|
||||
float LightFadeStart;
|
||||
float LightFadeEnd;
|
||||
std::uint32_t padding;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
#endif // ESM4_LIGHTING_H
|
124
components/esm4/loadachr.cpp
Normal file
124
components/esm4/loadachr.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadachr.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
//#include <iostream>
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::ActorCharacter::ActorCharacter() : mFormId(0), mFlags(0), mBaseObj(0),
|
||||
mScale(1.f), mOwner(0), mGlobal(0), mInitiallyDisabled(false)
|
||||
{
|
||||
mEditorId.clear();
|
||||
mFullName.clear();
|
||||
|
||||
mEsp.parent = 0;
|
||||
mEsp.flags = 0;
|
||||
}
|
||||
|
||||
ESM4::ActorCharacter::~ActorCharacter()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::ActorCharacter::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
mParent = reader.currCell(); // NOTE: only for persistent achr? (aren't they all persistent?)
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_FULL: reader.getZString(mFullName); break;
|
||||
case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break;
|
||||
case ESM4::SUB_DATA: reader.get(mPlacement); break;
|
||||
case ESM4::SUB_XSCL: reader.get(mScale); break;
|
||||
case ESM4::SUB_XOWN: reader.get(mOwner); break;
|
||||
case ESM4::SUB_XESP:
|
||||
{
|
||||
reader.get(mEsp);
|
||||
reader.adjustFormId(mEsp.parent);
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_XRGD: // ragdoll
|
||||
case ESM4::SUB_XRGB: // ragdoll biped
|
||||
{
|
||||
//std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_XHRS: // horse formId
|
||||
case ESM4::SUB_XMRC: // merchant container formId
|
||||
// TES5
|
||||
case ESM4::SUB_XAPD: // activation parent
|
||||
case ESM4::SUB_XAPR: // active parent
|
||||
case ESM4::SUB_XEZN: // encounter zone
|
||||
case ESM4::SUB_XHOR:
|
||||
case ESM4::SUB_XLCM: // levelled creature
|
||||
case ESM4::SUB_XLCN: // location
|
||||
case ESM4::SUB_XLKR: // location route?
|
||||
case ESM4::SUB_XLRT: // location type
|
||||
//
|
||||
case ESM4::SUB_XPRD:
|
||||
case ESM4::SUB_XPPA:
|
||||
case ESM4::SUB_INAM:
|
||||
case ESM4::SUB_PDTO:
|
||||
//
|
||||
case ESM4::SUB_XIS2:
|
||||
case ESM4::SUB_XPCI: // formId
|
||||
case ESM4::SUB_XLOD:
|
||||
case ESM4::SUB_VMAD:
|
||||
case ESM4::SUB_XLRL: // Unofficial Skyrim Patch
|
||||
case ESM4::SUB_XRDS: // FO3
|
||||
case ESM4::SUB_XIBS: // FO3
|
||||
case ESM4::SUB_SCHR: // FO3
|
||||
case ESM4::SUB_TNAM: // FO3
|
||||
case ESM4::SUB_XATO: // FONV
|
||||
{
|
||||
//std::cout << "ACHR " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::ACHR::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::ActorCharacter::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::ActorCharacter::blank()
|
||||
//{
|
||||
//}
|
70
components/esm4/loadachr.hpp
Normal file
70
components/esm4/loadachr.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ACHR_H
|
||||
#define ESM4_ACHR_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "reference.hpp" // FormId, Placement, EnableParent
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct ActorCharacter
|
||||
{
|
||||
FormId mParent; // cell formId, from the loading sequence
|
||||
// NOTE: for exterior cells it will be the dummy cell FormId
|
||||
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
std::string mFullName;
|
||||
FormId mBaseObj;
|
||||
|
||||
Placement mPlacement;
|
||||
float mScale; // default 1.f
|
||||
FormId mOwner;
|
||||
FormId mGlobal;
|
||||
|
||||
bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled)
|
||||
|
||||
EnableParent mEsp;
|
||||
|
||||
ActorCharacter();
|
||||
virtual ~ActorCharacter();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ACHR_H
|
106
components/esm4/loadacre.cpp
Normal file
106
components/esm4/loadacre.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadacre.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
//#include <iostream>
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::ActorCreature::ActorCreature() : mFormId(0), mFlags(0), mBaseObj(0), mScale(1.f),
|
||||
mOwner(0), mGlobal(0), mFactionRank(0), mInitiallyDisabled(false)
|
||||
{
|
||||
mEditorId.clear();
|
||||
|
||||
mEsp.parent = 0;
|
||||
mEsp.flags = 0;
|
||||
}
|
||||
|
||||
ESM4::ActorCreature::~ActorCreature()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::ActorCreature::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_NAME: reader.getFormId(mBaseObj); break;
|
||||
case ESM4::SUB_DATA: reader.get(mPlacement); break;
|
||||
case ESM4::SUB_XSCL: reader.get(mScale); break;
|
||||
case ESM4::SUB_XESP:
|
||||
{
|
||||
reader.get(mEsp);
|
||||
reader.adjustFormId(mEsp.parent);
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_XOWN: reader.getFormId(mOwner); break;
|
||||
case ESM4::SUB_XGLB: reader.get(mGlobal); break; // FIXME: formId?
|
||||
case ESM4::SUB_XRNK: reader.get(mFactionRank); break;
|
||||
case ESM4::SUB_XRGD: // ragdoll
|
||||
case ESM4::SUB_XRGB: // ragdoll biped
|
||||
{
|
||||
// seems to occur only for dead bodies, e.g. DeadMuffy, DeadDogVicious
|
||||
//std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_XLKR: // FO3
|
||||
case ESM4::SUB_XLCM: // FO3
|
||||
case ESM4::SUB_XEZN: // FO3
|
||||
case ESM4::SUB_XMRC: // FO3
|
||||
case ESM4::SUB_XAPD: // FO3
|
||||
case ESM4::SUB_XAPR: // FO3
|
||||
case ESM4::SUB_XRDS: // FO3
|
||||
case ESM4::SUB_XPRD: // FO3
|
||||
case ESM4::SUB_XATO: // FONV
|
||||
{
|
||||
//std::cout << "ACRE " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::ACRE::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::ActorCreature::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::ActorCreature::blank()
|
||||
//{
|
||||
//}
|
67
components/esm4/loadacre.hpp
Normal file
67
components/esm4/loadacre.hpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ACRE_H
|
||||
#define ESM4_ACRE_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "reference.hpp" // FormId, Placement, EnableParent
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct ActorCreature
|
||||
{
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
FormId mBaseObj;
|
||||
|
||||
Placement mPlacement;
|
||||
float mScale; // default 1.f
|
||||
FormId mOwner;
|
||||
FormId mGlobal;
|
||||
std::uint32_t mFactionRank;
|
||||
|
||||
bool mInitiallyDisabled; // TODO may need to check mFlags & 0x800 (initially disabled)
|
||||
|
||||
EnableParent mEsp;
|
||||
|
||||
ActorCreature();
|
||||
virtual ~ActorCreature();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ACRE_H
|
103
components/esm4/loadacti.cpp
Normal file
103
components/esm4/loadacti.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "acti.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream> // FIXME
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::Activator::Activator() : mFormId(0), mFlags(0), mScriptId(0), mLoopingSound(0), mActivationSound(0),
|
||||
mBoundRadius(0.f), mRadioTemplate(0), mRadioStation(0)
|
||||
{
|
||||
mEditorId.clear();
|
||||
mFullName.clear();
|
||||
mModel.clear();
|
||||
|
||||
mActivationPrompt.clear();
|
||||
}
|
||||
|
||||
ESM4::Activator::~Activator()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::Activator::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break;
|
||||
case ESM4::SUB_MODL: reader.getZString(mModel); break;
|
||||
case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break;
|
||||
case ESM4::SUB_SNAM: reader.getFormId(mLoopingSound); break;
|
||||
case ESM4::SUB_VNAM: reader.getFormId(mActivationSound); break;
|
||||
case ESM4::SUB_MODB: reader.get(mBoundRadius); break;
|
||||
case ESM4::SUB_INAM: reader.getFormId(mRadioTemplate); break; // FONV
|
||||
case ESM4::SUB_RNAM: reader.getFormId(mRadioStation); break;
|
||||
case ESM4::SUB_XATO: reader.getZString(mActivationPrompt); break; // FONV
|
||||
case ESM4::SUB_MODT:
|
||||
case ESM4::SUB_MODS:
|
||||
case ESM4::SUB_DEST:
|
||||
case ESM4::SUB_DMDL:
|
||||
case ESM4::SUB_DMDS:
|
||||
case ESM4::SUB_DMDT:
|
||||
case ESM4::SUB_DSTD:
|
||||
case ESM4::SUB_DSTF:
|
||||
case ESM4::SUB_FNAM:
|
||||
case ESM4::SUB_KNAM:
|
||||
case ESM4::SUB_KSIZ:
|
||||
case ESM4::SUB_KWDA:
|
||||
case ESM4::SUB_OBND:
|
||||
case ESM4::SUB_PNAM:
|
||||
case ESM4::SUB_VMAD:
|
||||
case ESM4::SUB_WNAM:
|
||||
{
|
||||
//std::cout << "ACTI " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::ACTI::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::Activator::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::Activator::blank()
|
||||
//{
|
||||
//}
|
121
components/esm4/loadalch.cpp
Normal file
121
components/esm4/loadalch.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadalch.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
//#include <iostream> // FIXME
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::Potion::Potion() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mScriptId(0), mBoundRadius(0.f)
|
||||
{
|
||||
mEditorId.clear();
|
||||
mFullName.clear();
|
||||
mModel.clear();
|
||||
mIcon.clear();
|
||||
mMiniIcon.clear();
|
||||
|
||||
mData.weight = 0.f;
|
||||
|
||||
std::memset(&mEffect, 0, sizeof(ScriptEffect));
|
||||
}
|
||||
|
||||
ESM4::Potion::~Potion()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::Potion::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break;
|
||||
case ESM4::SUB_MODL: reader.getZString(mModel); break;
|
||||
case ESM4::SUB_ICON: reader.getZString(mIcon); break;
|
||||
case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3
|
||||
case ESM4::SUB_DATA: reader.get(mData); break;
|
||||
case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break;
|
||||
case ESM4::SUB_MODB: reader.get(mBoundRadius); break;
|
||||
case ESM4::SUB_SCIT:
|
||||
{
|
||||
reader.get(mEffect);
|
||||
reader.adjustFormId(mEffect.formId);
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_ENIT:
|
||||
{
|
||||
if (subHdr.dataSize == 8) // TES4
|
||||
{
|
||||
reader.get(&mItem, 8);
|
||||
mItem.withdrawl = 0;
|
||||
mItem.sound = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
reader.get(mItem);
|
||||
reader.adjustFormId(mItem.withdrawl);
|
||||
reader.adjustFormId(mItem.sound);
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break;
|
||||
case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break;
|
||||
case ESM4::SUB_MODT:
|
||||
case ESM4::SUB_EFID:
|
||||
case ESM4::SUB_EFIT:
|
||||
case ESM4::SUB_CTDA:
|
||||
case ESM4::SUB_KSIZ:
|
||||
case ESM4::SUB_KWDA:
|
||||
case ESM4::SUB_MODS:
|
||||
case ESM4::SUB_OBND:
|
||||
case ESM4::SUB_ETYP: // FO3
|
||||
{
|
||||
//std::cout << "ALCH " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::ALCH::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::Potion::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::Potion::blank()
|
||||
//{
|
||||
//}
|
88
components/esm4/loadalch.hpp
Normal file
88
components/esm4/loadalch.hpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ALCH_H
|
||||
#define ESM4_ALCH_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "effect.hpp" // FormId, ScriptEffect
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct Potion
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct Data
|
||||
{
|
||||
float weight;
|
||||
};
|
||||
|
||||
struct EnchantedItem
|
||||
{
|
||||
std::int32_t value;
|
||||
std::uint32_t flags;
|
||||
FormId withdrawl;
|
||||
float chanceAddition;
|
||||
FormId sound;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
std::string mFullName;
|
||||
std::string mModel;
|
||||
std::string mIcon; // inventory
|
||||
std::string mMiniIcon; // inventory
|
||||
|
||||
FormId mPickUpSound;
|
||||
FormId mDropSound;
|
||||
|
||||
FormId mScriptId;
|
||||
ScriptEffect mEffect;
|
||||
|
||||
float mBoundRadius;
|
||||
|
||||
Data mData;
|
||||
EnchantedItem mItem;
|
||||
|
||||
Potion();
|
||||
virtual ~Potion();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ALCH_H
|
172
components/esm4/loadaloc.cpp
Normal file
172
components/esm4/loadaloc.cpp
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
Copyright (C) 2020-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadaloc.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
//#include <iostream> // FIXME: for debugging only
|
||||
//#include <iomanip> // FIXME: for debugging only
|
||||
|
||||
//#include <boost/scoped_array.hpp> // FIXME
|
||||
|
||||
//#include "formid.hpp" // FIXME:
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::MediaLocationController::MediaLocationController() : mFormId(0), mFlags(0),
|
||||
mConditionalFaction(0), mLocationDelay(0.f), mRetriggerDelay(0.f), mDayStart(0), mNightStart(0)
|
||||
{
|
||||
mEditorId.clear();
|
||||
mFullName.clear();
|
||||
|
||||
std::memset(&mMediaFlags, 0, sizeof(MLC_Flags));
|
||||
}
|
||||
|
||||
ESM4::MediaLocationController::~MediaLocationController()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::MediaLocationController::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_FULL: reader.getZString(mFullName); break;
|
||||
case ESM4::SUB_GNAM:
|
||||
{
|
||||
FormId id;
|
||||
reader.getFormId(id);
|
||||
mBattleSets.push_back(id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_LNAM:
|
||||
{
|
||||
FormId id;
|
||||
reader.getFormId(id);
|
||||
mLocationSets.push_back(id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_YNAM:
|
||||
{
|
||||
FormId id;
|
||||
reader.getFormId(id);
|
||||
mEnemySets.push_back(id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_HNAM:
|
||||
{
|
||||
FormId id;
|
||||
reader.getFormId(id);
|
||||
mNeutralSets.push_back(id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_XNAM:
|
||||
{
|
||||
FormId id;
|
||||
reader.getFormId(id);
|
||||
mFriendSets.push_back(id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_ZNAM:
|
||||
{
|
||||
FormId id;
|
||||
reader.getFormId(id);
|
||||
mAllySets.push_back(id);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_RNAM: reader.getFormId(mConditionalFaction); break;
|
||||
case ESM4::SUB_NAM1:
|
||||
{
|
||||
reader.get(mMediaFlags);
|
||||
std::uint8_t flags = mMediaFlags.loopingOptions;
|
||||
mMediaFlags.loopingOptions = (flags & 0xF0) >> 4;
|
||||
mMediaFlags.factionNotFound = flags & 0x0F; // WARN: overwriting data
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_NAM4: reader.get(mLocationDelay); break;
|
||||
case ESM4::SUB_NAM7: reader.get(mRetriggerDelay); break;
|
||||
case ESM4::SUB_NAM5: reader.get(mDayStart); break;
|
||||
case ESM4::SUB_NAM6: reader.get(mNightStart); break;
|
||||
case ESM4::SUB_NAM2: // always 0? 4 bytes
|
||||
case ESM4::SUB_NAM3: // always 0? 4 bytes
|
||||
case ESM4::SUB_FNAM: // always 0? 4 bytes
|
||||
{
|
||||
#if 0
|
||||
boost::scoped_array<unsigned char> mDataBuf(new unsigned char[subHdr.dataSize]);
|
||||
reader.get(&mDataBuf[0], subHdr.dataSize);
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << mEditorId << " " << ESM::printName(subHdr.typeId) << ":size " << subHdr.dataSize << "\n";
|
||||
for (std::size_t i = 0; i < subHdr.dataSize; ++i)
|
||||
{
|
||||
//if (mDataBuf[i] > 64 && mDataBuf[i] < 91) // looks like printable ascii char
|
||||
//ss << (char)(mDataBuf[i]) << " ";
|
||||
//else
|
||||
ss << std::setfill('0') << std::setw(2) << std::hex << (int)(mDataBuf[i]);
|
||||
if ((i & 0x000f) == 0xf) // wrap around
|
||||
ss << "\n";
|
||||
else if (i < subHdr.dataSize-1)
|
||||
ss << " ";
|
||||
}
|
||||
std::cout << ss.str() << std::endl;
|
||||
#else
|
||||
//std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..."
|
||||
//<< subHdr.dataSize << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
//std::cout << "ALOC " << ESM::printName(subHdr.typeId) << " skipping..."
|
||||
//<< subHdr.dataSize << std::endl;
|
||||
//reader.skipSubRecordData();
|
||||
throw std::runtime_error("ESM4::ALOC::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::MediaLocationController::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::MediaLocationController::blank()
|
||||
//{
|
||||
//}
|
88
components/esm4/loadaloc.hpp
Normal file
88
components/esm4/loadaloc.hpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
Copyright (C) 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ALOC_H
|
||||
#define ESM4_ALOC_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct MLC_Flags
|
||||
{
|
||||
// use day/night transition: 0 = loop, 1 = random, 2 = retrigger, 3 = none
|
||||
// use defaults (6:00/23:54): 4 = loop, 5 = random, 6 = retrigger, 7 = none
|
||||
std::uint8_t loopingOptions;
|
||||
// 0 = neutral, 1 = enemy, 2 = ally, 3 = friend, 4 = location, 5 = none
|
||||
std::uint8_t factionNotFound; // WARN: overwriting whatever is in this
|
||||
std::uint16_t unknown; // padding?
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct MediaLocationController
|
||||
{
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
std::string mFullName;
|
||||
|
||||
std::vector<FormId> mBattleSets;
|
||||
std::vector<FormId> mLocationSets;
|
||||
std::vector<FormId> mEnemySets;
|
||||
std::vector<FormId> mNeutralSets;
|
||||
std::vector<FormId> mFriendSets;
|
||||
std::vector<FormId> mAllySets;
|
||||
|
||||
MLC_Flags mMediaFlags;
|
||||
|
||||
FormId mConditionalFaction;
|
||||
|
||||
float mLocationDelay;
|
||||
float mRetriggerDelay;
|
||||
|
||||
std::uint32_t mDayStart;
|
||||
std::uint32_t mNightStart;
|
||||
|
||||
MediaLocationController();
|
||||
virtual ~MediaLocationController();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ALOC_H
|
133
components/esm4/loadammo.cpp
Normal file
133
components/esm4/loadammo.cpp
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadammo.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::Ammunition::Ammunition() : mFormId(0), mFlags(0), mPickUpSound(0), mDropSound(0), mBoundRadius(0.f)
|
||||
{
|
||||
mEditorId.clear();
|
||||
mFullName.clear();
|
||||
mModel.clear();
|
||||
mText.clear();
|
||||
mIcon.clear();
|
||||
mMiniIcon.clear();
|
||||
}
|
||||
|
||||
ESM4::Ammunition::~Ammunition()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::Ammunition::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
std::uint32_t esmVer = reader.esmVersion();
|
||||
bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134;
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break;
|
||||
case ESM4::SUB_DATA:
|
||||
{
|
||||
//if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170)
|
||||
if (subHdr.dataSize == 16) // FO3 has 13 bytes even though VER_094
|
||||
{
|
||||
FormId projectile;
|
||||
reader.get(projectile); // FIXME: add to mData
|
||||
reader.get(mData.flags);
|
||||
reader.get(mData.weight);
|
||||
float damageInFloat;
|
||||
reader.get(damageInFloat); // FIXME: add to mData
|
||||
}
|
||||
else if (isFONV || subHdr.dataSize == 13)
|
||||
{
|
||||
reader.get(mData.speed);
|
||||
std::uint8_t flags;
|
||||
reader.get(flags);
|
||||
mData.flags = flags;
|
||||
static std::uint8_t dummy;
|
||||
reader.get(dummy);
|
||||
reader.get(dummy);
|
||||
reader.get(dummy);
|
||||
reader.get(mData.value);
|
||||
reader.get(mData.clipRounds);
|
||||
}
|
||||
else // TES4
|
||||
{
|
||||
reader.get(mData.speed);
|
||||
reader.get(mData.flags);
|
||||
reader.get(mData.value);
|
||||
reader.get(mData.weight);
|
||||
reader.get(mData.damage);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_ICON: reader.getZString(mIcon); break;
|
||||
case ESM4::SUB_MICO: reader.getZString(mMiniIcon); break; // FO3
|
||||
case ESM4::SUB_MODL: reader.getZString(mModel); break;
|
||||
case ESM4::SUB_ANAM: reader.get(mEnchantmentPoints); break;
|
||||
case ESM4::SUB_ENAM: reader.getFormId(mEnchantment); break;
|
||||
case ESM4::SUB_MODB: reader.get(mBoundRadius); break;
|
||||
case ESM4::SUB_DESC: reader.getLocalizedString(mText); break;
|
||||
case ESM4::SUB_YNAM: reader.getFormId(mPickUpSound); break;
|
||||
case ESM4::SUB_ZNAM: reader.getFormId(mDropSound); break;
|
||||
case ESM4::SUB_MODT:
|
||||
case ESM4::SUB_OBND:
|
||||
case ESM4::SUB_KSIZ:
|
||||
case ESM4::SUB_KWDA:
|
||||
case ESM4::SUB_ONAM: // FO3
|
||||
case ESM4::SUB_DAT2: // FONV
|
||||
case ESM4::SUB_QNAM: // FONV
|
||||
case ESM4::SUB_RCIL: // FONV
|
||||
case ESM4::SUB_SCRI: // FONV
|
||||
{
|
||||
//std::cout << "AMMO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::AMMO::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::Ammunition::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::Ammunition::blank()
|
||||
//{
|
||||
//}
|
84
components/esm4/loadammo.hpp
Normal file
84
components/esm4/loadammo.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018-2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_AMMO_H
|
||||
#define ESM4_AMMO_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct Ammunition
|
||||
{
|
||||
struct Data // FIXME: TES5 projectile, damage (float)
|
||||
{
|
||||
float speed;
|
||||
std::uint32_t flags;
|
||||
std::uint32_t value; // gold
|
||||
float weight;
|
||||
std::uint16_t damage;
|
||||
std::uint8_t clipRounds; // only in FO3/FONV
|
||||
|
||||
Data() : speed(0.f), flags(0), value(0), weight(0.f), damage(0), clipRounds(0) {}
|
||||
};
|
||||
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
std::string mFullName;
|
||||
std::string mModel;
|
||||
std::string mText;
|
||||
std::string mIcon; // inventory
|
||||
std::string mMiniIcon; // inventory
|
||||
|
||||
FormId mPickUpSound;
|
||||
FormId mDropSound;
|
||||
|
||||
float mBoundRadius;
|
||||
|
||||
std::uint16_t mEnchantmentPoints;
|
||||
FormId mEnchantment;
|
||||
|
||||
Data mData;
|
||||
|
||||
Ammunition();
|
||||
virtual ~Ammunition();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_AMMO_H
|
80
components/esm4/loadanio.cpp
Normal file
80
components/esm4/loadanio.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadanio.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::AnimObject::AnimObject() : mFormId(0), mFlags(0), mBoundRadius(0.f), mIdleAnim(0)
|
||||
{
|
||||
mEditorId.clear();
|
||||
mModel.clear();
|
||||
mUnloadEvent.clear();
|
||||
}
|
||||
|
||||
ESM4::AnimObject::~AnimObject()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::AnimObject::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_MODL: reader.getZString(mModel); break;
|
||||
case ESM4::SUB_BNAM: reader.getZString(mUnloadEvent); break;
|
||||
case ESM4::SUB_DATA: reader.getFormId(mIdleAnim); break;
|
||||
case ESM4::SUB_MODB: reader.get(mBoundRadius); break;
|
||||
case ESM4::SUB_MODT: // TES5 only
|
||||
case ESM4::SUB_MODS: // TES5 only
|
||||
{
|
||||
//std::cout << "ANIO " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::ANIO::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::AnimObject::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::AnimObject::blank()
|
||||
//{
|
||||
//}
|
63
components/esm4/loadanio.hpp
Normal file
63
components/esm4/loadanio.hpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018, 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ANIO_H
|
||||
#define ESM4_ANIO_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct AnimObject
|
||||
{
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
std::string mModel;
|
||||
|
||||
float mBoundRadius;
|
||||
|
||||
FormId mIdleAnim; // only in TES4
|
||||
std::string mUnloadEvent; // only in TES5
|
||||
|
||||
AnimObject();
|
||||
virtual ~AnimObject();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ANIO_H
|
106
components/esm4/loadappa.cpp
Normal file
106
components/esm4/loadappa.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018-2021 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadappa.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::Apparatus::Apparatus() : mFormId(0), mFlags(0), mBoundRadius(0.f), mScriptId(0)
|
||||
{
|
||||
mEditorId.clear();
|
||||
mFullName.clear();
|
||||
mModel.clear();
|
||||
mText.clear();
|
||||
mIcon.clear();
|
||||
|
||||
mData.type = 0;
|
||||
mData.value = 0;
|
||||
mData.weight = 0.f;
|
||||
mData.quality = 0.f;
|
||||
}
|
||||
|
||||
ESM4::Apparatus::~Apparatus()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::Apparatus::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break;
|
||||
case ESM4::SUB_DATA:
|
||||
{
|
||||
if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170)
|
||||
{
|
||||
reader.get(mData.value);
|
||||
reader.get(mData.weight);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.get(mData.type);
|
||||
reader.get(mData.value);
|
||||
reader.get(mData.weight);
|
||||
reader.get(mData.quality);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_ICON: reader.getZString(mIcon); break;
|
||||
case ESM4::SUB_MODL: reader.getZString(mModel); break;
|
||||
case ESM4::SUB_SCRI: reader.getFormId(mScriptId); break;
|
||||
case ESM4::SUB_MODB: reader.get(mBoundRadius); break;
|
||||
case ESM4::SUB_DESC: reader.getLocalizedString(mText); break;
|
||||
case ESM4::SUB_MODT:
|
||||
case ESM4::SUB_OBND:
|
||||
case ESM4::SUB_QUAL:
|
||||
{
|
||||
//std::cout << "APPA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::APPA::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::Apparatus::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::Apparatus::blank()
|
||||
//{
|
||||
//}
|
75
components/esm4/loadappa.hpp
Normal file
75
components/esm4/loadappa.hpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright (C) 2016, 2018-2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_APPA_H
|
||||
#define ESM4_APPA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "formid.hpp"
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct Apparatus
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
std::uint8_t type; // 0 = Mortar and Pestle, 1 = Alembic, 2 = Calcinator, 3 = Retort
|
||||
std::uint32_t value; // gold
|
||||
float weight;
|
||||
float quality;
|
||||
};
|
||||
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
std::string mFullName;
|
||||
std::string mModel;
|
||||
std::string mText;
|
||||
std::string mIcon; // inventory
|
||||
|
||||
float mBoundRadius;
|
||||
|
||||
FormId mScriptId;
|
||||
|
||||
Data mData;
|
||||
|
||||
Apparatus();
|
||||
virtual ~Apparatus();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_APPA_H
|
151
components/esm4/loadarma.cpp
Normal file
151
components/esm4/loadarma.cpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
Copyright (C) 2019, 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#include "loadarma.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
//#include <iostream> // FIXME: testing only
|
||||
|
||||
#include "reader.hpp"
|
||||
//#include "writer.hpp"
|
||||
|
||||
ESM4::ArmorAddon::ArmorAddon() : mFormId(0), mFlags(0), mTextureMale(0), mTextureFemale(0),
|
||||
mRacePrimary(0)
|
||||
{
|
||||
mEditorId.clear();
|
||||
|
||||
mModelMale.clear();
|
||||
mModelFemale.clear();
|
||||
}
|
||||
|
||||
ESM4::ArmorAddon::~ArmorAddon()
|
||||
{
|
||||
}
|
||||
|
||||
void ESM4::ArmorAddon::load(ESM4::Reader& reader)
|
||||
{
|
||||
mFormId = reader.hdr().record.id;
|
||||
reader.adjustFormId(mFormId);
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
std::uint32_t esmVer = reader.esmVersion();
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
switch (subHdr.typeId)
|
||||
{
|
||||
case ESM4::SUB_EDID: reader.getZString(mEditorId); break;
|
||||
case ESM4::SUB_MOD2: reader.getZString(mModelMale); break;
|
||||
case ESM4::SUB_MOD3: reader.getZString(mModelFemale); break;
|
||||
case ESM4::SUB_MOD4:
|
||||
case ESM4::SUB_MOD5:
|
||||
{
|
||||
std::string model;
|
||||
reader.getZString(model);
|
||||
|
||||
//std::cout << mEditorId << " " << ESM::printName(subHdr.typeId) << " " << model << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_NAM0: reader.getFormId(mTextureMale); break;
|
||||
case ESM4::SUB_NAM1: reader.getFormId(mTextureFemale); break;
|
||||
case ESM4::SUB_RNAM: reader.getFormId(mRacePrimary); break;
|
||||
case ESM4::SUB_MODL:
|
||||
{
|
||||
if ((esmVer == ESM::VER_094 || esmVer == ESM::VER_170) && subHdr.dataSize == 4) // TES5
|
||||
{
|
||||
FormId formId;
|
||||
reader.getFormId(formId);
|
||||
mRaces.push_back(formId);
|
||||
}
|
||||
else
|
||||
reader.skipSubRecordData(); // FIXME: this should be mModelMale for FO3/FONV
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_BODT: // body template
|
||||
{
|
||||
reader.get(mBodyTemplate.bodyPart);
|
||||
reader.get(mBodyTemplate.flags);
|
||||
reader.get(mBodyTemplate.unknown1); // probably padding
|
||||
reader.get(mBodyTemplate.unknown2); // probably padding
|
||||
reader.get(mBodyTemplate.unknown3); // probably padding
|
||||
reader.get(mBodyTemplate.type);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_BOD2: // TES5
|
||||
{
|
||||
reader.get(mBodyTemplate.bodyPart);
|
||||
mBodyTemplate.flags = 0;
|
||||
mBodyTemplate.unknown1 = 0; // probably padding
|
||||
mBodyTemplate.unknown2 = 0; // probably padding
|
||||
mBodyTemplate.unknown3 = 0; // probably padding
|
||||
reader.get(mBodyTemplate.type);
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM4::SUB_DNAM:
|
||||
case ESM4::SUB_MO2T: // FIXME: should group with MOD2
|
||||
case ESM4::SUB_MO2S: // FIXME: should group with MOD2
|
||||
case ESM4::SUB_MO3T: // FIXME: should group with MOD3
|
||||
case ESM4::SUB_MO3S: // FIXME: should group with MOD3
|
||||
case ESM4::SUB_MOSD: // FO3 // FIXME: should group with MOD3
|
||||
case ESM4::SUB_MO4T: // FIXME: should group with MOD4
|
||||
case ESM4::SUB_MO4S: // FIXME: should group with MOD4
|
||||
case ESM4::SUB_MO5T:
|
||||
case ESM4::SUB_NAM2: // txst formid male
|
||||
case ESM4::SUB_NAM3: // txst formid female
|
||||
case ESM4::SUB_SNDD: // footset sound formid
|
||||
case ESM4::SUB_BMDT: // FO3
|
||||
case ESM4::SUB_DATA: // FO3
|
||||
case ESM4::SUB_ETYP: // FO3
|
||||
case ESM4::SUB_FULL: // FO3
|
||||
case ESM4::SUB_ICO2: // FO3 // female
|
||||
case ESM4::SUB_ICON: // FO3 // male
|
||||
case ESM4::SUB_MODT: // FO3 // FIXME: should group with MODL
|
||||
case ESM4::SUB_MODS: // FO3 // FIXME: should group with MODL
|
||||
case ESM4::SUB_MODD: // FO3 // FIXME: should group with MODL
|
||||
case ESM4::SUB_OBND: // FO3
|
||||
{
|
||||
//std::cout << "ARMA " << ESM::printName(subHdr.typeId) << " skipping..." << std::endl;
|
||||
reader.skipSubRecordData();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("ESM4::ARMA::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//void ESM4::ArmorAddon::save(ESM4::Writer& writer) const
|
||||
//{
|
||||
//}
|
||||
|
||||
//void ESM4::ArmorAddon::blank()
|
||||
//{
|
||||
//}
|
70
components/esm4/loadarma.hpp
Normal file
70
components/esm4/loadarma.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Copyright (C) 2019, 2020 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
trial & error. See http://en.uesp.net/wiki for details.
|
||||
|
||||
*/
|
||||
#ifndef ESM4_ARMA_H
|
||||
#define ESM4_ARMA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "formid.hpp"
|
||||
#include "actor.hpp" // BodyTemplate
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
class Reader;
|
||||
class Writer;
|
||||
|
||||
struct ArmorAddon
|
||||
{
|
||||
FormId mFormId; // from the header
|
||||
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
|
||||
|
||||
std::string mEditorId;
|
||||
|
||||
std::string mModelMale;
|
||||
std::string mModelFemale;
|
||||
|
||||
FormId mTextureMale;
|
||||
FormId mTextureFemale;
|
||||
|
||||
FormId mRacePrimary;
|
||||
std::vector<FormId> mRaces; // TES5 only
|
||||
|
||||
BodyTemplate mBodyTemplate; // TES5
|
||||
|
||||
ArmorAddon();
|
||||
virtual ~ArmorAddon();
|
||||
|
||||
virtual void load(ESM4::Reader& reader);
|
||||
//virtual void save(ESM4::Writer& writer) const;
|
||||
|
||||
//void blank();
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ESM4_ARMA_H
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue