Merge branch 'master' into 'sort_me_baby_one_more_time'

# Conflicts:
#   components/nif/niffile.cpp
C++20
psi29a 3 years ago
commit b011809056

@ -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)
{
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
if (!isCommanded(actor))
return;
bool hasCommandPackage = false;
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
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())
{
mLastAiPackage = AiPackageTypeId::None;
return;
}
// Players don't use this.
return;
}
auto packageIt = mPackages.begin();
MWMechanics::AiPackage* package = packageIt->get();
if (!package->alwaysActive() && outOfRange)
return;
if (mPackages.empty())
{
mLastAiPackage = AiPackageTypeId::None;
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();
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 nearestDist = std::numeric_limits<float>::max();
osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3();
float bestRating = 0.f;
float bestRating = 0.f;
for (auto it = mPackages.begin(); it != mPackages.end();)
{
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
for (auto it = mPackages.begin(); it != mPackages.end();)
{
if ((*it)->getTypeId() != AiPackageTypeId::Combat) break;
MWWorld::Ptr target = (*it)->getTarget();
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);
// target disappeared (e.g. summoned creatures)
if (target.isEmpty())
{
it = erase(it);
}
else
{
float rating = MWMechanics::getBestActionRating(actor, target);
const ESM::Position &targetPos = target.getRefData().getPosition();
const ESM::Position &targetPos = target.getRefData().getPosition();
float distTo = (targetPos.asVec3() - vActorPos).length2();
float distTo = (targetPos.asVec3() - vActorPos).length2();
// Small threshold for changing target
if (it == mPackages.begin())
distTo = std::max(0.f, distTo - 2500.f);
// 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;
// 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);
}
if (mPackages.empty())
return;
packageIt = mPackages.begin();
package = packageIt->get();
packageTypeId = package->getTypeId();
if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat)
{
assert(itActualCombat != mPackages.end());
// move combat package with nearest target to the front
std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat));
}
try
package = mPackages.front().get();
packageTypeId = package->getTypeId();
}
try
{
if (package->execute(actor, characterController, mAiState, duration))
{
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;
}
else
// Put repeating non-combat AI packages on the end of the stack so they can be used again
if (isActualAiPackage(packageTypeId) && package->getRepeat())
{
mDone = false;
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;
}
catch (std::exception& e)
else
{
Log(Debug::Error) << "Error during AiSequence::execute: " << e.what();
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(); }
void erase(std::list<std::shared_ptr<AiPackage>>::const_iterator package);
std::list<std::shared_ptr<AiPackage>>& getUnderlyingList() { return mPackages; }
AiPackages::const_iterator begin() const { return mPackages.begin(); }
AiPackages::const_iterator end() const { return mPackages.end(); }
/// 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());
}
/// 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;
/// Set base, modified and current to \a value.
void set (const T& value);
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 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)
{
Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found";
return;
}
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream);
if (!result.success())
if (mWritePng != nullptr)
{
Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status();
mWritePng->waitTillDone();
map.mImageData = std::move(mWritePng->mImageData);
mWritePng = nullptr;
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)

@ -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()));

@ -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);
}
}

@ -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
{

@ -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?
}
}

@ -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

@ -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

@ -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

@ -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");
}
}

@ -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

@ -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

@ -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

@ -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));
}
}

@ -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

@ -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

@ -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

@ -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()
//{
//}

@ -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

@ -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()
//{
//}

@ -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

@ -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()
//{
//}

@ -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()
//{
//}

@ -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

@ -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()
//{
//}

@ -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

@ -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()
//{
//}

@ -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

@ -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()
//{
//}

@ -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

@ -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()
//{
//}

@ -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

@ -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()
//{
//}

@ -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…
Cancel
Save