1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-02 10:11:33 +00:00

Merge branch 'master' of gitlab.com:openmw/openmw into lua_class_data

This commit is contained in:
Zackhasacat 2023-11-16 07:37:03 -06:00
commit 10030a55e0
165 changed files with 1373 additions and 921 deletions

1
.gitignore vendored
View file

@ -28,6 +28,7 @@ Doxygen
.idea
cmake-build-*
files/windows/*.aps
.cache/clangd
## qt-creator
CMakeLists.txt.user*
.vs

View file

@ -14,6 +14,7 @@
Bug #4754: Stack of ammunition cannot be equipped partially
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses
Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation
Bug #5129: Stuttering animation on Centurion Archer
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
Bug #5371: Keyframe animation tracks are used for any file that begins with an X
@ -24,6 +25,7 @@
Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6025: Subrecords cannot overlap records
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex
Bug #6190: Unintuitive sun specularity time of day dependence
Bug #6222: global map cell size can crash openmw if set to too high a value
Bug #6313: Followers with high Fight can turn hostile
Bug #6427: Enemy health bar disappears before damaging effect ends
@ -68,6 +70,7 @@
Bug #7298: Water ripples from projectiles sometimes are not spawned
Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes
Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives
Bug #7380: NiZBufferProperty issue
Bug #7413: Generated wilderness cells don't spawn fish
Bug #7415: Unbreakable lock discrepancies
Bug #7428: AutoCalc flag is not used to calculate enchantment costs
@ -90,11 +93,15 @@
Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat
Bug #7642: Items in repair and recharge menus aren't sorted alphabetically
Bug #7647: NPC walk cycle bugs after greeting player
Bug #7654: Tooltips for enchantments with invalid effects cause crashes
Bug #7660: Some inconsistencies regarding Invisibility breaking
Bug #7675: Successful lock spell doesn't produce a sound
Bug #7679: Scene luminance value flashes when toggling shaders
Feature #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds

View file

@ -86,7 +86,7 @@ declare -rA GROUPED_DEPS=(
libswresample3
libswscale5
libtinyxml2.6.2v5
libyaml-cpp0.7
libyaml-cpp0.8
python3-pip
xvfb
"
@ -125,3 +125,4 @@ add-apt-repository -y ppa:openmw/openmw
add-apt-repository -y ppa:openmw/openmw-daily
add-apt-repository -y ppa:openmw/staging
apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null
apt list --installed

View file

@ -1204,7 +1204,8 @@ namespace EsmTool
std::array<std::string_view, 10> weathers
= { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" };
for (size_t i = 0; i < weathers.size(); ++i)
std::cout << " " << weathers[i] << ": " << mData.mData.mProbabilities[i] << std::endl;
std::cout << " " << weathers[i] << ": " << static_cast<unsigned>(mData.mData.mProbabilities[i])
<< std::endl;
std::cout << " Map Color: " << mData.mMapColor << std::endl;
if (!mData.mSleepList.empty())
std::cout << " Sleep List: " << mData.mSleepList << std::endl;

View file

@ -9,15 +9,14 @@ namespace ESSImport
void convertInventory(const Inventory& inventory, ESM::InventoryState& state)
{
int index = 0;
uint32_t index = 0;
for (const auto& item : inventory.mItems)
{
ESM::ObjectState objstate;
objstate.blank();
objstate.mRef = item;
objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId);
objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile
// openmw handles them differently, so no need to set any flags
objstate.mCount = item.mCount;
state.mItems.push_back(objstate);
if (item.mRelativeEquipmentSlot != -1)
// Note we should really write the absolute slot here, which we do not know about

View file

@ -26,7 +26,7 @@
#include <components/files/qtconversion.hpp>
#include <components/misc/strings/conversion.hpp>
#include <components/navmeshtool/protocol.hpp>
#include <components/settings/settings.hpp>
#include <components/settings/values.hpp>
#include <components/vfs/bsaarchive.hpp>
#include "utils/profilescombobox.hpp"
@ -123,7 +123,7 @@ namespace Launcher
int getMaxNavMeshDbFileSizeMiB()
{
return Settings::Manager::getUInt64("max navmeshdb file size", "Navigator") / (1024 * 1024);
return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024);
}
std::optional<QString> findFirstPath(const QStringList& directories, const QString& fileName)
@ -359,9 +359,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
void Launcher::DataFilesPage::saveSettings(const QString& profile)
{
if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB())
Settings::Manager::setUInt64(
"max navmeshdb file size", "Navigator", static_cast<std::uint64_t>(std::max(0, value)) * 1024 * 1024);
Settings::navigator().mMaxNavmeshdbFileSize.set(
static_cast<std::uint64_t>(std::max(0, ui.navMeshMaxSizeSpinBox->value())) * 1024 * 1024);
QString profileName = profile;

View file

@ -158,32 +158,32 @@ bool Launcher::GraphicsPage::loadSettings()
lightingMethodComboBox->setCurrentIndex(lightingMethod);
// Shadows
if (Settings::Manager::getBool("actor shadows", "Shadows"))
if (Settings::shadows().mActorShadows)
actorShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("player shadows", "Shadows"))
if (Settings::shadows().mPlayerShadows)
playerShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("terrain shadows", "Shadows"))
if (Settings::shadows().mTerrainShadows)
terrainShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("object shadows", "Shadows"))
if (Settings::shadows().mObjectShadows)
objectShadowsCheckBox->setCheckState(Qt::Checked);
if (Settings::Manager::getBool("enable indoor shadows", "Shadows"))
if (Settings::shadows().mEnableIndoorShadows)
indoorShadowsCheckBox->setCheckState(Qt::Checked);
shadowComputeSceneBoundsComboBox->setCurrentIndex(shadowComputeSceneBoundsComboBox->findText(
QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str()))));
shadowComputeSceneBoundsComboBox->setCurrentIndex(
shadowComputeSceneBoundsComboBox->findText(QString(tr(Settings::shadows().mComputeSceneBounds.get().c_str()))));
int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows");
const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance;
if (shadowDistLimit > 0)
{
shadowDistanceCheckBox->setCheckState(Qt::Checked);
shadowDistanceSpinBox->setValue(shadowDistLimit);
}
float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows");
const float shadowFadeStart = Settings::shadows().mShadowFadeStart;
if (shadowFadeStart != 0)
fadeStartSpinBox->setValue(shadowFadeStart);
int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows");
const int shadowRes = Settings::shadows().mShadowMapResolution;
int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes));
if (shadowResIndex != -1)
shadowResolutionComboBox->setCurrentIndex(shadowResIndex);
@ -240,55 +240,36 @@ void Launcher::GraphicsPage::saveSettings()
Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]);
// Shadows
int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist)
Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist);
float cFadeStart = fadeStartSpinBox->value();
if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart)
Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart);
const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0;
Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist);
const float cFadeStart = fadeStartSpinBox->value();
if (cShadowDist > 0)
Settings::shadows().mShadowFadeStart.set(cFadeStart);
bool cActorShadows = actorShadowsCheckBox->checkState();
bool cObjectShadows = objectShadowsCheckBox->checkState();
bool cTerrainShadows = terrainShadowsCheckBox->checkState();
bool cPlayerShadows = playerShadowsCheckBox->checkState();
const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked;
const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked;
if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows)
{
if (!Settings::Manager::getBool("enable shadows", "Shadows"))
Settings::Manager::setBool("enable shadows", "Shadows", true);
if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows)
Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows);
if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows)
Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows);
if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows)
Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows);
if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows)
Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows);
Settings::shadows().mEnableShadows.set(true);
Settings::shadows().mActorShadows.set(cActorShadows);
Settings::shadows().mPlayerShadows.set(cPlayerShadows);
Settings::shadows().mObjectShadows.set(cObjectShadows);
Settings::shadows().mTerrainShadows.set(cTerrainShadows);
}
else
{
if (Settings::Manager::getBool("enable shadows", "Shadows"))
Settings::Manager::setBool("enable shadows", "Shadows", false);
if (Settings::Manager::getBool("actor shadows", "Shadows"))
Settings::Manager::setBool("actor shadows", "Shadows", false);
if (Settings::Manager::getBool("player shadows", "Shadows"))
Settings::Manager::setBool("player shadows", "Shadows", false);
if (Settings::Manager::getBool("object shadows", "Shadows"))
Settings::Manager::setBool("object shadows", "Shadows", false);
if (Settings::Manager::getBool("terrain shadows", "Shadows"))
Settings::Manager::setBool("terrain shadows", "Shadows", false);
Settings::shadows().mEnableShadows.set(false);
Settings::shadows().mActorShadows.set(false);
Settings::shadows().mPlayerShadows.set(false);
Settings::shadows().mObjectShadows.set(false);
Settings::shadows().mTerrainShadows.set(false);
}
bool cIndoorShadows = indoorShadowsCheckBox->checkState();
if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows)
Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows);
int cShadowRes = shadowResolutionComboBox->currentText().toInt();
if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows"))
Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes);
auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString();
if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows"))
Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds);
Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked);
Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt());
Settings::shadows().mComputeSceneBounds.set(shadowComputeSceneBoundsComboBox->currentText().toStdString());
}
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)

View file

@ -191,6 +191,7 @@ bool Launcher::SettingsPage::loadSettings()
}
loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox);
loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox);
loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox);
distantLandCheckBox->setCheckState(
Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked);
@ -338,6 +339,7 @@ void Launcher::SettingsPage::saveSettings()
saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing);
saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection);
saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement);
saveSettingBool(*playerMovementIgnoresAnimationCheckBox, Settings::game().mPlayerMovementIgnoresAnimation);
const bool wantDistantLand = distantLandCheckBox->checkState() == Qt::Checked;
if (wantDistantLand != (Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging))

View file

@ -318,9 +318,14 @@ bool OMW::Engine::frame(float frametime)
mViewer->eventTraversal();
mViewer->updateTraversal();
// update GUI by world data
{
ScopedProfile<UserStatsType::WindowManager> profile(frameStart, frameNumber, *timer, *stats);
mWorld->updateWindowManager();
if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{
mWorld->updateWindowManager();
}
}
mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now

View file

@ -34,11 +34,26 @@ namespace MWClass
static const ESM4::Npc* chooseTemplate(const std::vector<const ESM4::Npc*>& recs, uint16_t flag)
{
// In case of FO3 the function may return nullptr that will lead to "ESM4 NPC traits not found"
// exception and the NPC will not be added to the scene. But in any way it shouldn't cause a crash.
for (const auto* rec : recs)
if (rec->mIsTES4 || rec->mIsFONV || !(rec->mBaseConfig.tes5.templateFlags & flag))
{
if (rec->mIsTES4)
return rec;
else if (rec->mIsFONV)
{
// TODO: FO3 should use this branch as well. But it is not clear how to distinguish FO3 from
// TES5. Currently FO3 uses wrong template flags that can lead to "ESM4 NPC traits not found"
// exception the NPC will not be added to the scene. But in any way it shouldn't cause a crash.
if (!(rec->mBaseConfig.fo3.templateFlags & flag))
return rec;
}
else if (rec->mIsFO4)
{
if (!(rec->mBaseConfig.fo4.templateFlags & flag))
return rec;
}
else if (!(rec->mBaseConfig.tes5.templateFlags & flag))
return rec;
}
return nullptr;
}
@ -75,8 +90,8 @@ namespace MWClass
const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore();
auto npcRecs = withBaseTemplates<ESM4::LevelledNpc, ESM4::Npc>(ptr.get<ESM4::Npc>()->mBase);
data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseTraits);
data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseBaseData);
data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits);
data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData);
if (!data->mTraits)
throw std::runtime_error("ESM4 NPC traits not found");
@ -88,10 +103,13 @@ namespace MWClass
data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female;
else if (data->mTraits->mIsFONV)
data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female;
else if (data->mTraits->mIsFO4)
data->mIsFemale
= data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5
else
data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female;
if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseInventory))
if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::Template_UseInventory))
{
for (const ESM4::InventoryItem& item : inv->mInventory)
{

View file

@ -556,6 +556,20 @@ namespace MWGui
std::unique_ptr<MWWorld::Action> action = ptr.getClass().use(ptr, force);
action->execute(player);
// Handles partial equipping (final part)
if (mEquippedStackableCount.has_value())
{
// the count to unequip
int count = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value();
if (count > 0)
{
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
invStore.unequipItemQuantity(ptr, count);
updateItemView();
}
mEquippedStackableCount.reset();
}
if (isVisible())
{
mItemView->update();
@ -581,27 +595,21 @@ namespace MWGui
}
// Handles partial equipping
const std::pair<std::vector<int>, bool> slots = ptr.getClass().getEquipmentSlots(ptr);
mEquippedStackableCount.reset();
const auto slots = ptr.getClass().getEquipmentSlots(ptr);
if (!slots.first.empty() && slots.second)
{
int equippedStackableCount = 0;
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator slotIt = invStore.getSlot(slots.first.front());
// Get the count before useItem()
// Save the currently equipped count before useItem()
if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId())
equippedStackableCount = slotIt->getRefData().getCount();
useItem(ptr);
int unequipCount = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - equippedStackableCount;
if (unequipCount > 0)
{
invStore.unequipItemQuantity(ptr, unequipCount);
updateItemView();
}
mEquippedStackableCount = slotIt->getRefData().getCount();
else
mEquippedStackableCount = 0;
}
else
MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false);
MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false);
// If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1
// item

View file

@ -74,6 +74,7 @@ namespace MWGui
DragAndDrop* mDragAndDrop;
int mSelectedItem;
std::optional<int> mEquippedStackableCount;
MWWorld::Ptr mPtr;

View file

@ -183,6 +183,10 @@ namespace MWGui
return switchFocus(D_Down, false);
case MyGUI::KeyCode::Tab:
return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true);
case MyGUI::KeyCode::Period:
return switchFocus(D_Prev, true);
case MyGUI::KeyCode::Slash:
return switchFocus(D_Next, true);
case MyGUI::KeyCode::Return:
case MyGUI::KeyCode::NumpadEnter:
case MyGUI::KeyCode::Space:

View file

@ -413,8 +413,8 @@ namespace MWGui
text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n";
if (mCurrentSlot->mProfile.mMaximumHealth > 0)
text << std::fixed << std::setprecision(0) << "#{sHealth} " << mCurrentSlot->mProfile.mCurrentHealth << "/"
<< mCurrentSlot->mProfile.mMaximumHealth << "\n";
text << "#{sHealth} " << static_cast<int>(mCurrentSlot->mProfile.mCurrentHealth) << "/"
<< static_cast<int>(mCurrentSlot->mProfile.mMaximumHealth) << "\n";
text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCellName << "}\n";

View file

@ -355,12 +355,10 @@ namespace MWGui::Widgets
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().search(mEffectParams.mEffectID);
const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().find(mEffectParams.mEffectID);
const ESM::Attribute* attribute = store.get<ESM::Attribute>().search(mEffectParams.mAttribute);
const ESM::Skill* skill = store.get<ESM::Skill>().search(mEffectParams.mSkill);
assert(magicEffect);
auto windowManager = MWBase::Environment::get().getWindowManager();
std::string_view pt = windowManager->getGameSettingString("spoint", {});

View file

@ -1100,7 +1100,7 @@ namespace MWGui
std::string_view settingSection = tag.substr(0, comma_pos);
std::string_view settingTag = tag.substr(comma_pos + 1, tag.length());
_result = Settings::Manager::getString(settingTag, settingSection);
_result = Settings::get<MyGUI::Colour>(settingSection, settingTag).get().print();
}
else if (tag.starts_with(tokenToFind))
{
@ -1115,7 +1115,7 @@ namespace MWGui
else
{
std::vector<std::string> split;
Misc::StringUtils::split(std::string{ tag }, split, ":");
Misc::StringUtils::split(tag, split, ":");
l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager();

View file

@ -344,6 +344,12 @@ namespace MWLua
playerScripts->uiModeChanged(argId, false);
}
void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force)
{
MWBase::Environment::get().getWorldModel()->registerPtr(object);
mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force });
}
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.

View file

@ -77,10 +77,7 @@ namespace MWLua
{
mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) });
}
void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override
{
mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force });
}
void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override;
void exteriorCreated(MWWorld::CellStore& cell) override
{
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });

View file

@ -140,7 +140,7 @@ namespace MWLua
= sol::overload([](const GlobalStore& store, std::string_view globalId, float val) {
auto g = store.search(ESM::RefId::deserializeText(globalId));
if (g == nullptr)
return;
throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore");
char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId);
if (varType == 's' || varType == 'l')
{

View file

@ -457,6 +457,9 @@ namespace MWLua
return nullptr;
return &*rec.mSchool;
});
skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string {
return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText();
});
auto schoolT = context.mLua->sol().new_usertype<ESM::MagicSchool>("MagicSchool");
schoolT[sol::meta_function::to_string]

View file

@ -395,6 +395,11 @@ namespace MWLua
return dist <= actorsProcessingRange;
};
actor["isDead"] = [](const Object& o) {
const auto& target = o.ptr();
return target.getClass().getCreatureStats(target).isDead();
};
actor["getEncumbrance"] = [](const Object& actor) -> float {
const MWWorld::Ptr ptr = actor.ptr();
return ptr.getClass().getEncumbrance(ptr);

View file

@ -39,6 +39,6 @@ namespace MWMechanics
{
const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects();
return (magicEffects.getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude() > 0)
|| (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() > 75);
|| (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() >= 75);
}
}

View file

@ -2386,49 +2386,55 @@ namespace MWMechanics
}
}
osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration);
if (duration > 0.0f)
moved /= duration;
else
moved = osg::Vec3f(0.f, 0.f, 0.f);
osg::Vec3f movementFromAnimation
= mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration);
moved.x() *= scale;
moved.y() *= scale;
// Ensure we're moving in generally the right direction...
if (speed > 0.f && moved != osg::Vec3f())
if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying())
{
float l = moved.length();
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2
|| std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2
|| std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2)
{
moved = movement;
// For some creatures getSpeed doesn't work, so we adjust speed to the animation.
// TODO: Fix Creature::getSpeed.
float newLength = moved.length();
if (newLength > 0 && !cls.isNpc())
moved *= (l / newLength);
}
}
if (duration > 0.0f)
movementFromAnimation /= duration;
else
movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f);
if (mFloatToSurface && cls.isActor())
{
if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode
&& cls.getCreatureStats(mPtr)
.getMagicEffects()
.getOrDefault(ESM::MagicEffect::Paralyze)
.getModifier()
> 0))
{
moved.z() = 1.0;
}
}
movementFromAnimation.x() *= scale;
movementFromAnimation.y() *= scale;
// Update movement
if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying())
world->queueMovement(mPtr, moved);
if (speed > 0.f && movementFromAnimation != osg::Vec3f())
{
// Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from
// animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive
// diagonal movement, we have to rotate the movement taken from the animation to the intended
// direction.
//
// Note that while a complete movement animation cycle will have a well defined direction, no individual
// frame will, and therefore we have to determine the direction based on the currently playing cycle
// instead.
float animMovementAngle = getAnimationMovementDirection();
float targetMovementAngle = std::atan2(-movement.x(), movement.y());
float diff = targetMovementAngle - animMovementAngle;
movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation;
}
if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation))
movement = movementFromAnimation;
if (mFloatToSurface)
{
if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode
&& cls.getCreatureStats(mPtr)
.getMagicEffects()
.getOrDefault(ESM::MagicEffect::Paralyze)
.getModifier()
> 0))
{
movement.z() = 1.0;
}
}
// Update movement
world->queueMovement(mPtr, movement);
}
mSkipAnim = false;
@ -2909,6 +2915,39 @@ namespace MWMechanics
MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch);
}
float CharacterController::getAnimationMovementDirection() const
{
switch (mMovementState)
{
case CharState_RunLeft:
case CharState_SneakLeft:
case CharState_SwimWalkLeft:
case CharState_SwimRunLeft:
case CharState_WalkLeft:
return osg::PI_2f;
case CharState_RunRight:
case CharState_SneakRight:
case CharState_SwimWalkRight:
case CharState_SwimRunRight:
case CharState_WalkRight:
return -osg::PI_2f;
case CharState_RunForward:
case CharState_SneakForward:
case CharState_SwimRunForward:
case CharState_SwimWalkForward:
case CharState_WalkForward:
return mAnimation->getLegsYawRadians();
case CharState_RunBack:
case CharState_SneakBack:
case CharState_SwimWalkBack:
case CharState_SwimRunBack:
case CharState_WalkBack:
return mAnimation->getLegsYawRadians() - osg::PIf;
default:
return 0.0f;
}
}
void CharacterController::updateHeadTracking(float duration)
{
const osg::Node* head = mAnimation->getNode("Bip01 Head");

View file

@ -319,6 +319,8 @@ namespace MWMechanics
void playSwishSound() const;
float getAnimationMovementDirection() const;
MWWorld::MovementDirectionFlags getSupportedMovementDirections() const;
};
}

View file

@ -932,6 +932,9 @@ namespace MWMechanics
if (target.getCellRef().getLockLevel()
< magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude
{
MWBase::Environment::get().getSoundManager()->playSound3D(
target, ESM::RefId::stringRefId("Open Lock"), 1.f, 1.f);
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}");
target.getCellRef().lock(magnitude);

View file

@ -1235,9 +1235,11 @@ namespace MWRender
mRootController->setEnabled(enable);
if (enable)
{
mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1))
* osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0)));
osg::Quat legYaw = osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1));
mRootController->setRotate(legYaw * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0)));
yawOffset = mLegsYawRadians;
// When yawing the root, also update the accumulated movement.
movement = legYaw * movement;
}
}
if (mSpineController)

View file

@ -247,7 +247,7 @@ namespace MWRender
defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
stateset->setAttribute(defaultMat);
SceneUtil::ShadowManager::disableShadowsForStateSet(stateset);
SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset);
// assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG)

View file

@ -124,6 +124,8 @@ namespace MWRender
auto findArmorAddons = [&](const ESM4::Armor* armor) {
for (ESM::FormId armaId : armor->mAddOns)
{
if (armaId.isZeroOrUnset())
continue;
const ESM4::ArmorAddon* arma = store->get<ESM4::ArmorAddon>().search(armaId);
if (!arma)
{

View file

@ -763,7 +763,7 @@ namespace MWRender
lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
SceneUtil::ShadowManager::disableShadowsForStateSet(stateset);
SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset);
// override sun for local map
SceneUtil::configureStateSetSunOverride(static_cast<SceneUtil::LightManager*>(mSceneRoot), light, stateset);

View file

@ -20,11 +20,6 @@ namespace MWRender
mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment));
mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment));
}
void LuminanceCalculator::compile()
{
int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight);
for (auto& buffer : mBuffers)
{
@ -38,7 +33,6 @@ namespace MWRender
osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST);
buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);
buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight);
buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels);
buffer.luminanceTex = new osg::Texture2D;
buffer.luminanceTex->setInternalFormat(GL_R16F);
@ -62,14 +56,6 @@ namespace MWRender
buffer.luminanceProxyFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.luminanceProxyTex));
buffer.resolveSceneLumFbo = new osg::FrameBufferObject;
buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1));
buffer.sceneLumFbo = new osg::FrameBufferObject;
buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex));
buffer.sceneLumSS = new osg::StateSet;
buffer.sceneLumSS->setAttributeAndModes(mLuminanceProgram);
buffer.sceneLumSS->addUniform(new osg::Uniform("sceneTex", 0));
@ -84,6 +70,26 @@ namespace MWRender
mBuffers[0].resolveSS->setTextureAttributeAndModes(1, mBuffers[1].luminanceTex);
mBuffers[1].resolveSS->setTextureAttributeAndModes(1, mBuffers[0].luminanceTex);
}
void LuminanceCalculator::compile()
{
int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight);
for (auto& buffer : mBuffers)
{
buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight);
buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels);
buffer.mipmappedSceneLuminanceTex->dirtyTextureObject();
buffer.resolveSceneLumFbo = new osg::FrameBufferObject;
buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1));
buffer.sceneLumFbo = new osg::FrameBufferObject;
buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex));
}
mCompiled = true;
}
@ -114,13 +120,14 @@ namespace MWRender
buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
if (dirty)
if (mIsBlank)
{
// Use current frame data for previous frame to warm up calculations and prevent popin
mBuffers[(frameId + 1) % 2].resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
mIsBlank = false;
}
buffer.resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);

View file

@ -58,6 +58,7 @@ namespace MWRender
bool mCompiled = false;
bool mEnabled = false;
bool mIsBlank = true;
int mWidth = 1;
int mHeight = 1;

View file

@ -10,9 +10,11 @@
namespace MWRender
{
PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager)
PingPongCanvas::PingPongCanvas(
Shader::ShaderManager& shaderManager, const std::shared_ptr<LuminanceCalculator>& luminanceCalculator)
: mFallbackStateSet(new osg::StateSet)
, mMultiviewResolveStateSet(new osg::StateSet)
, mLuminanceCalculator(luminanceCalculator)
{
setUseDisplayList(false);
setUseVertexBufferObjects(true);
@ -26,8 +28,7 @@ namespace MWRender
addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3));
mLuminanceCalculator = LuminanceCalculator(shaderManager);
mLuminanceCalculator.disable();
mLuminanceCalculator->disable();
Shader::ShaderManager::DefineMap defines;
Stereo::shaderStereoDefines(defines);
@ -142,7 +143,7 @@ namespace MWRender
.getTexture());
}
mLuminanceCalculator.dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight());
mLuminanceCalculator->dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight());
if (Stereo::getStereo())
mRenderViewport
@ -158,11 +159,11 @@ namespace MWRender
{ GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT },
{ GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT } } };
(mAvgLum) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable();
(mAvgLum) ? mLuminanceCalculator->enable() : mLuminanceCalculator->disable();
// A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly
// supported, so that's what we use for now.
mLuminanceCalculator.draw(*this, renderInfo, state, ext, frameId);
mLuminanceCalculator->draw(*this, renderInfo, state, ext, frameId);
auto buffer = buffers[0];
@ -195,6 +196,39 @@ namespace MWRender
}
};
// When textures are created (or resized) we need to either dirty them and/or clear them.
// Otherwise, there will be undefined behavior when reading from a texture that has yet to be written to in a
// later pass.
for (const auto& attachment : mDirtyAttachments)
{
const auto [w, h]
= attachment.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight());
attachment.mTarget->setTextureSize(w, h);
if (attachment.mMipMap)
attachment.mTarget->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h));
attachment.mTarget->dirtyTextureObject();
osg::ref_ptr<osg::FrameBufferObject> fbo = new osg::FrameBufferObject;
fbo->setAttachment(
osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(attachment.mTarget));
fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
glViewport(0, 0, attachment.mTarget->getTextureWidth(), attachment.mTarget->getTextureHeight());
state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT);
glClearColor(attachment.mClearColor.r(), attachment.mClearColor.g(), attachment.mClearColor.b(),
attachment.mClearColor.a());
glClear(GL_COLOR_BUFFER_BIT);
if (attachment.mTarget->getNumMipmapLevels() > 0)
{
state.setActiveTextureUnit(0);
state.applyTextureAttribute(0, attachment.mTarget);
ext->glGenerateMipmap(GL_TEXTURE_2D);
}
}
for (const size_t& index : filtered)
{
const auto& node = mPasses[index];
@ -202,8 +236,8 @@ namespace MWRender
node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, mTextureDepth);
if (mAvgLum)
node.mRootStateSet->setTextureAttribute(
PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId));
node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation,
mLuminanceCalculator->getLuminanceTexture(frameId));
if (mTextureNormals)
node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals);
@ -238,6 +272,23 @@ namespace MWRender
if (pass.mRenderTarget)
{
if (mDirtyAttachments.size() > 0)
{
const auto [w, h]
= pass.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight());
// Custom render targets must be shared between frame ids, so it's impossible to double buffer
// without expensive copies. That means the only thread-safe place to resize is in the draw
// thread.
osg::Texture2D* texture = const_cast<osg::Texture2D*>(dynamic_cast<const osg::Texture2D*>(
pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0)
.getTexture()));
texture->setTextureSize(w, h);
texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels());
texture->dirtyTextureObject();
}
pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
if (pass.mRenderTexture->getNumMipmapLevels() > 0)
@ -311,5 +362,7 @@ namespace MWRender
{
bindDestinationFbo();
}
mDirtyAttachments.clear();
}
}

View file

@ -22,7 +22,8 @@ namespace MWRender
class PingPongCanvas : public osg::Geometry
{
public:
PingPongCanvas(Shader::ShaderManager& shaderManager);
PingPongCanvas(
Shader::ShaderManager& shaderManager, const std::shared_ptr<LuminanceCalculator>& luminanceCalculator);
void drawGeometry(osg::RenderInfo& renderInfo) const;
@ -30,6 +31,11 @@ namespace MWRender
void dirty() { mDirty = true; }
void setDirtyAttachments(const std::vector<fx::Types::RenderTarget>& attachments)
{
mDirtyAttachments = attachments;
}
const fx::DispatchArray& getPasses() { return mPasses; }
void setPasses(fx::DispatchArray&& passes);
@ -65,11 +71,12 @@ namespace MWRender
osg::ref_ptr<osg::Texture> mTextureNormals;
mutable bool mDirty = false;
mutable std::vector<fx::Types::RenderTarget> mDirtyAttachments;
mutable osg::ref_ptr<osg::Viewport> mRenderViewport;
mutable osg::ref_ptr<osg::FrameBufferObject> mMultiviewResolveFramebuffer;
mutable osg::ref_ptr<osg::FrameBufferObject> mDestinationFBO;
mutable std::array<osg::ref_ptr<osg::FrameBufferObject>, 3> mFbos;
mutable LuminanceCalculator mLuminanceCalculator;
mutable std::shared_ptr<LuminanceCalculator> mLuminanceCalculator;
};
}

View file

@ -118,9 +118,14 @@ namespace MWRender
, mUsePostProcessing(Settings::postProcessing().mEnabled)
, mSamples(Settings::video().mAntialiasing)
, mPingPongCull(new PingPongCull(this))
, mCanvases({ new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()),
new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()) })
{
auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager();
std::shared_ptr<LuminanceCalculator> luminanceCalculator = std::make_shared<LuminanceCalculator>(shaderManager);
for (auto& canvas : mCanvases)
canvas = new PingPongCanvas(shaderManager, luminanceCalculator);
mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER);
mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0));
@ -139,8 +144,7 @@ namespace MWRender
if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass)
{
mTransparentDepthPostPass
= new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(),
Settings::postProcessing().mTransparentPostpass);
= new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass);
osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass);
}
@ -211,25 +215,14 @@ namespace MWRender
if (Stereo::getStereo())
Stereo::Manager::instance().screenResolutionChanged();
auto width = renderWidth();
auto height = renderHeight();
for (auto& technique : mTechniques)
{
for (auto& [name, rt] : technique->getRenderTargetsMap())
{
const auto [w, h] = rt.mSize.get(width, height);
rt.mTarget->setTextureSize(w, h);
}
}
size_t frameId = frame() % 2;
createObjectsForFrame(frameId);
mRendering.updateProjectionMatrix();
mRendering.setScreenRes(width, height);
mRendering.setScreenRes(renderWidth(), renderHeight());
dirtyTechniques();
dirtyTechniques(true);
mDirty = true;
mDirtyFrameId = !frameId;
@ -534,7 +527,7 @@ namespace MWRender
mCanvases[frameId]->dirty();
}
void PostProcessor::dirtyTechniques()
void PostProcessor::dirtyTechniques(bool dirtyAttachments)
{
size_t frameId = frame() % 2;
@ -548,6 +541,8 @@ namespace MWRender
mNormals = false;
mPassLights = false;
std::vector<fx::Types::RenderTarget> attachmentsToDirty;
for (const auto& technique : mTechniques)
{
if (!technique->isValid())
@ -613,8 +608,6 @@ namespace MWRender
uniform->mName.c_str(), *type, uniform->getNumElements()));
}
std::unordered_map<osg::Texture2D*, osg::Texture2D*> renderTargetCache;
for (const auto& pass : technique->getPasses())
{
int subTexUnit = texUnit;
@ -626,32 +619,39 @@ namespace MWRender
if (!pass->getTarget().empty())
{
const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()];
const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight());
subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget);
renderTargetCache[rt.mTarget] = subPass.mRenderTexture;
subPass.mRenderTexture->setTextureSize(w, h);
subPass.mRenderTexture->setName(std::string(pass->getTarget()));
if (rt.mMipMap)
subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h));
auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()];
subPass.mSize = renderTarget.mSize;
subPass.mRenderTexture = renderTarget.mTarget;
subPass.mMipMap = renderTarget.mMipMap;
subPass.mRenderTarget = new osg::FrameBufferObject;
subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0,
osg::FrameBufferAttachment(subPass.mRenderTexture));
const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight());
subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h));
if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(),
[renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; })
== attachmentsToDirty.cend())
{
attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget));
}
}
for (const auto& whitelist : pass->getRenderTargets())
for (const auto& name : pass->getRenderTargets())
{
auto it = technique->getRenderTargetsMap().find(whitelist);
if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget])
auto& renderTarget = technique->getRenderTargetsMap()[name];
subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget);
subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit));
if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(),
[renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; })
== attachmentsToDirty.cend())
{
subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]);
subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++));
attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget));
}
subTexUnit++;
}
node.mPasses.emplace_back(std::move(subPass));
@ -668,6 +668,9 @@ namespace MWRender
hud->updateTechniques();
mRendering.getSkyManager()->setSunglare(sunglare);
if (dirtyAttachments)
mCanvases[frameId]->setDirtyAttachments(attachmentsToDirty);
}
PostProcessor::Status PostProcessor::enableTechnique(
@ -681,7 +684,7 @@ namespace MWRender
int pos = std::min<int>(location.value_or(mTechniques.size()), mTechniques.size());
mTechniques.insert(mTechniques.begin() + pos, technique);
dirtyTechniques();
dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug);
return Status_Toggled;
}
@ -774,7 +777,7 @@ namespace MWRender
for (auto& technique : mTemplates)
technique->compile();
dirtyTechniques();
dirtyTechniques(true);
}
void PostProcessor::disableDynamicShaders()

View file

@ -204,7 +204,7 @@ namespace MWRender
void createObjectsForFrame(size_t frameId);
void dirtyTechniques();
void dirtyTechniques(bool dirtyAttachments = false);
void update(size_t frameId);

View file

@ -331,8 +331,8 @@ namespace MWRender
// Shadows and radial fog have problems with fixed-function mode.
bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog
|| Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders
|| Settings::Manager::getBool("enable shadows", "Shadows")
|| lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ || mSkyBlending || Stereo::getMultiview();
|| Settings::shadows().mEnableShadows || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ
|| mSkyBlending || Stereo::getMultiview();
resourceSystem->getSceneManager()->setForceShaders(forceShaders);
// FIXME: calling dummy method because terrain needs to know whether lighting is clamped
@ -367,22 +367,22 @@ namespace MWRender
sceneRoot->setName("Scene Root");
int shadowCastingTraversalMask = Mask_Scene;
if (Settings::Manager::getBool("actor shadows", "Shadows"))
if (Settings::shadows().mActorShadows)
shadowCastingTraversalMask |= Mask_Actor;
if (Settings::Manager::getBool("player shadows", "Shadows"))
if (Settings::shadows().mPlayerShadows)
shadowCastingTraversalMask |= Mask_Player;
int indoorShadowCastingTraversalMask = shadowCastingTraversalMask;
if (Settings::Manager::getBool("object shadows", "Shadows"))
if (Settings::shadows().mObjectShadows)
shadowCastingTraversalMask |= (Mask_Object | Mask_Static);
if (Settings::Manager::getBool("terrain shadows", "Shadows"))
if (Settings::shadows().mTerrainShadows)
shadowCastingTraversalMask |= Mask_Terrain;
mShadowManager = std::make_unique<SceneUtil::ShadowManager>(sceneRoot, mRootNode, shadowCastingTraversalMask,
indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static,
indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, Settings::shadows(),
mResourceSystem->getSceneManager()->getShaderManager());
Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines();
Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(Settings::shadows());
Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines();
Shader::ShaderManager::DefineMap globalDefines
= mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines();
@ -702,7 +702,7 @@ namespace MWRender
{
// need to wrap this in a StateUpdater?
mSunLight->setDiffuse(diffuse);
mSunLight->setSpecular(specular);
mSunLight->setSpecular(osg::Vec4f(specular.x(), specular.y(), specular.z(), specular.w() * sunVis));
mPostProcessor->getStateUpdater()->setSunColor(diffuse);
mPostProcessor->getStateUpdater()->setSunVis(sunVis);
@ -770,7 +770,7 @@ namespace MWRender
if (enabled)
mShadowManager->enableOutdoorMode();
else
mShadowManager->enableIndoorMode();
mShadowManager->enableIndoorMode(Settings::shadows());
mPostProcessor->getStateUpdater()->setIsInterior(!enabled);
}
@ -1319,6 +1319,7 @@ namespace MWRender
const float lodFactor = Settings::terrain().mLodFactor;
const bool groundcover = Settings::groundcover().mEnabled;
const bool distantTerrain = Settings::terrain().mDistantTerrain;
const double expiryDelay = Settings::cells().mCacheExpiryDelay;
if (distantTerrain || groundcover)
{
const int compMapResolution = Settings::terrain().mCompositeMapResolution;
@ -1329,7 +1330,7 @@ namespace MWRender
const bool debugChunks = Settings::terrain().mDebugChunks;
auto quadTreeWorld = std::make_unique<Terrain::QuadTreeWorld>(mSceneRoot, mRootNode, mResourceSystem,
mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel,
lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace);
lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace, expiryDelay);
if (Settings::terrain().mObjectPaging)
{
newChunkMgr.mObjectPaging
@ -1351,7 +1352,7 @@ namespace MWRender
}
else
newChunkMgr.mTerrain = std::make_unique<Terrain::TerrainGrid>(mSceneRoot, mRootNode, mResourceSystem,
mTerrainStorage.get(), Mask_Terrain, worldspace, Mask_PreCompile, Mask_Debug);
mTerrainStorage.get(), Mask_Terrain, worldspace, expiryDelay, Mask_PreCompile, Mask_Debug);
newChunkMgr.mTerrain->setTargetFrameRate(Settings::cells().mTargetFramerate);
float distanceMult = std::cos(osg::DegreesToRadians(std::min(mFieldOfView, 140.f)) / 2.f);

View file

@ -220,7 +220,7 @@ namespace
camera->setNodeMask(MWRender::Mask_RenderToTexture);
camera->setCullMask(MWRender::Mask_Sky);
camera->addChild(mEarlyRenderBinRoot);
SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet());
SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet());
}
private:
@ -271,7 +271,7 @@ namespace MWRender
if (!mSceneManager->getForceShaders())
skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(),
osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON);
SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet());
SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *skyroot->getOrCreateStateSet());
parentNode->addChild(skyroot);
mEarlyRenderBinRoot = new osg::Group;

View file

@ -265,7 +265,8 @@ namespace MWRender
camera->setNodeMask(Mask_RenderToTexture);
if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709
SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet());
SceneUtil::ShadowManager::disableShadowsForStateSet(
Settings::shadows(), *camera->getOrCreateStateSet());
}
void apply(osg::Camera* camera) override
@ -341,7 +342,7 @@ namespace MWRender
camera->addChild(mClipCullNode);
camera->setNodeMask(Mask_RenderToTexture);
SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet());
SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet());
}
void apply(osg::Camera* camera) override

View file

@ -111,12 +111,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState(
}
void MWWorld::ContainerStore::storeEquipmentState(
const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const
const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const
{
}
void MWWorld::ContainerStore::readEquipmentState(
const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory)
const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory)
{
}
@ -128,7 +128,7 @@ void MWWorld::ContainerStore::storeState(const LiveCellRef<T>& ref, ESM::ObjectS
template <typename T>
void MWWorld::ContainerStore::storeStates(
const CellRefList<T>& collection, ESM::InventoryState& inventory, int& index, bool equipable) const
const CellRefList<T>& collection, ESM::InventoryState& inventory, size_t& index, bool equipable) const
{
for (const LiveCellRef<T>& liveCellRef : collection.mList)
{
@ -926,7 +926,7 @@ void MWWorld::ContainerStore::writeState(ESM::InventoryState& state) const
{
state.mItems.clear();
int index = 0;
size_t index = 0;
storeStates(potions, state, index);
storeStates(appas, state, index);
storeStates(armors, state, index, true);
@ -947,12 +947,12 @@ void MWWorld::ContainerStore::readState(const ESM::InventoryState& inventory)
mModified = true;
mResolved = true;
int index = 0;
size_t index = 0;
for (const ESM::ObjectState& state : inventory.mItems)
{
int type = MWBase::Environment::get().getESMStore()->find(state.mRef.mRefID);
int thisIndex = index++;
size_t thisIndex = index++;
switch (type)
{

View file

@ -161,16 +161,16 @@ namespace MWWorld
void storeState(const LiveCellRef<T>& ref, ESM::ObjectState& state) const;
template <typename T>
void storeStates(
const CellRefList<T>& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const;
void storeStates(const CellRefList<T>& collection, ESM::InventoryState& inventory, size_t& index,
bool equipable = false) const;
void updateRechargingItems();
virtual void storeEquipmentState(
const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const;
const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const;
virtual void readEquipmentState(
const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory);
const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory);
public:
ContainerStore();

View file

@ -138,6 +138,59 @@ namespace
return npcsToReplace;
}
template <class RecordType>
std::vector<RecordType> getSpellsToReplace(
const MWWorld::Store<RecordType>& spells, const MWWorld::Store<ESM::MagicEffect>& magicEffects)
{
std::vector<RecordType> spellsToReplace;
for (RecordType spell : spells)
{
if (spell.mEffects.mList.empty())
continue;
bool changed = false;
auto iter = spell.mEffects.mList.begin();
while (iter != spell.mEffects.mList.end())
{
const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID);
if (!mgef)
{
Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId
<< ": dropping invalid effect (index " << iter->mEffectID << ")";
iter = spell.mEffects.mList.erase(iter);
changed = true;
continue;
}
if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1)
{
iter->mAttribute = -1;
Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId
<< ": dropping unexpected attribute argument of "
<< ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect";
changed = true;
}
if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1)
{
iter->mSkill = -1;
Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId
<< ": dropping unexpected skill argument of "
<< ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect";
changed = true;
}
++iter;
}
if (changed)
spellsToReplace.emplace_back(spell);
}
return spellsToReplace;
}
// Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no
// longer exists however. So instead of removing the item altogether, we're only removing the script.
template <class MapT>
@ -538,71 +591,24 @@ namespace MWWorld
removeMissingScripts(getWritable<ESM::Script>(), getWritable<ESM::Creature>().mStatic);
// Validate spell effects for invalid arguments
std::vector<ESM::Spell> spellsToReplace;
// Validate spell effects and enchantments for invalid arguments
auto& spells = getWritable<ESM::Spell>();
for (ESM::Spell spell : spells)
{
if (spell.mEffects.mList.empty())
continue;
bool changed = false;
auto iter = spell.mEffects.mList.begin();
while (iter != spell.mEffects.mList.end())
{
const ESM::MagicEffect* mgef = getWritable<ESM::MagicEffect>().search(iter->mEffectID);
if (!mgef)
{
Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index "
<< iter->mEffectID << ") present. Dropping the effect.";
iter = spell.mEffects.mList.erase(iter);
changed = true;
continue;
}
if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill)
{
if (iter->mAttribute != -1)
{
iter->mAttribute = -1;
Log(Debug::Verbose)
<< ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId
<< "' has an attribute argument present. Dropping the argument.";
changed = true;
}
}
else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute)
{
if (iter->mSkill != -1)
{
iter->mSkill = -1;
Log(Debug::Verbose)
<< ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId
<< "' has a skill argument present. Dropping the argument.";
changed = true;
}
}
else if (iter->mSkill != -1 || iter->mAttribute != -1)
{
iter->mSkill = -1;
iter->mAttribute = -1;
Log(Debug::Verbose) << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '"
<< spell.mId << "' has argument(s) present. Dropping the argument(s).";
changed = true;
}
++iter;
}
if (changed)
spellsToReplace.emplace_back(spell);
}
auto& enchantments = getWritable<ESM::Enchantment>();
auto& magicEffects = getWritable<ESM::MagicEffect>();
std::vector<ESM::Spell> spellsToReplace = getSpellsToReplace(spells, magicEffects);
for (const ESM::Spell& spell : spellsToReplace)
{
spells.eraseStatic(spell.mId);
spells.insertStatic(spell);
}
std::vector<ESM::Enchantment> enchantmentsToReplace = getSpellsToReplace(enchantments, magicEffects);
for (const ESM::Enchantment& enchantment : enchantmentsToReplace)
{
enchantments.eraseStatic(enchantment.mId);
enchantments.insertStatic(enchantment);
}
}
void ESMStore::movePlayerRecord()

View file

@ -46,32 +46,32 @@ void MWWorld::InventoryStore::initSlots(TSlots& slots_)
}
void MWWorld::InventoryStore::storeEquipmentState(
const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const
const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const
{
for (int i = 0; i < static_cast<int>(mSlots.size()); ++i)
for (int32_t i = 0; i < MWWorld::InventoryStore::Slots; ++i)
{
if (mSlots[i].getType() != -1 && mSlots[i]->getBase() == &ref)
{
inventory.mEquipmentSlots[index] = i;
}
inventory.mEquipmentSlots[static_cast<uint32_t>(index)] = i;
}
if (mSelectedEnchantItem.getType() != -1 && mSelectedEnchantItem->getBase() == &ref)
inventory.mSelectedEnchantItem = index;
}
void MWWorld::InventoryStore::readEquipmentState(
const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory)
const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory)
{
if (index == inventory.mSelectedEnchantItem)
mSelectedEnchantItem = iter;
std::map<int, int>::const_iterator found = inventory.mEquipmentSlots.find(index);
auto found = inventory.mEquipmentSlots.find(index);
if (found != inventory.mEquipmentSlots.end())
{
if (found->second < 0 || found->second >= MWWorld::InventoryStore::Slots)
throw std::runtime_error("Invalid slot index in inventory state");
// make sure the item can actually be equipped in this slot
int slot = found->second;
int32_t slot = found->second;
std::pair<std::vector<int>, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter);
if (!allowedSlots.first.size())
return;

View file

@ -81,9 +81,9 @@ namespace MWWorld
void fireEquipmentChangedEvent();
void storeEquipmentState(
const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override;
const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const override;
void readEquipmentState(
const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) override;
const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) override;
ContainerStoreIterator findSlot(int slot) const;

View file

@ -789,8 +789,7 @@ namespace MWWorld
mRendering.configureFog(
mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset / 100.0f, mResult.mFogColor);
mRendering.setAmbientColour(mResult.mAmbientColor);
mRendering.setSunColour(
mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade, mResult.mGlareView * glareFade);
mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor, mResult.mGlareView * glareFade);
mRendering.getSkyManager()->setWeather(mResult);

View file

@ -660,8 +660,8 @@ namespace MWWorld
std::string_view World::getCellName(const MWWorld::Cell& cell) const
{
if (!cell.isExterior() || !cell.getNameId().empty())
return cell.getNameId();
if (!cell.isExterior() || !cell.getDisplayName().empty())
return cell.getDisplayName();
return ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); },

View file

@ -411,7 +411,7 @@ namespace
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_compound_shape_and_box_inside)
TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_bounding_box_data)
{
mNode.mName = "Bounding Box";
mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV;
@ -427,15 +427,11 @@ namespace
Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
expected.mCollisionShape.reset(shape.release());
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_compound_shape_with_box_inside)
TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_bounding_box_data)
{
mNode.mName = "Bounding Box";
mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV;
@ -453,15 +449,11 @@ namespace
Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
expected.mCollisionShape.reset(shape.release());
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_but_should_use_bounding_box)
TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_should_use_bounding_box)
{
mNode.mName = "Bounding Box";
mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV;
@ -483,10 +475,6 @@ namespace
Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
expected.mCollisionShape.reset(shape.release());
EXPECT_EQ(*result, expected);
}
@ -519,10 +507,6 @@ namespace
Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(1, 2, 3)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release());
expected.mCollisionShape.reset(shape.release());
EXPECT_EQ(*result, expected);
}
@ -555,10 +539,6 @@ namespace
Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6);
expected.mCollisionBox.mCenter = osg::Vec3f(-4, -5, -6);
std::unique_ptr<btBoxShape> box(new btBoxShape(btVector3(4, 5, 6)));
std::unique_ptr<btCompoundShape> shape(new btCompoundShape);
shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release());
expected.mCollisionShape.reset(shape.release());
EXPECT_EQ(*result, expected);
}
@ -977,12 +957,12 @@ namespace
}
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
for_root_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
{
mNiStringExtraData.mData = "NCC__";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
@ -1005,13 +985,13 @@ namespace
}
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
for_root_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
{
mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.mData = "NCC__";
mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
@ -1032,8 +1012,62 @@ namespace
EXPECT_EQ(*result, expected);
}
TEST_F(
TestBulletNifLoader, for_root_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
{
mNiStringExtraData.mData = "NC___";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&mNiNode);
file.mHash = mHash;
const auto result = mLoader.load(file);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
std::unique_ptr<btCompoundShape> compound(new btCompoundShape);
compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true));
Resource::BulletShape expected;
expected.mCollisionShape.reset(compound.release());
expected.mVisualCollisionType = Resource::VisualCollisionType::Default;
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
for_root_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
{
mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.mData = "NC___";
mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&mNiNode);
file.mHash = mHash;
const auto result = mLoader.load(file);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
std::unique_ptr<btCompoundShape> compound(new btCompoundShape);
compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true));
Resource::BulletShape expected;
expected.mCollisionShape.reset(compound.release());
expected.mVisualCollisionType = Resource::VisualCollisionType::Default;
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_should_ignore_extra_data)
{
mNiStringExtraData.mData = "NC___";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
@ -1054,35 +1088,6 @@ namespace
Resource::BulletShape expected;
expected.mCollisionShape.reset(compound.release());
expected.mVisualCollisionType = Resource::VisualCollisionType::Default;
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
{
mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.mData = "NC___";
mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&mNiNode);
file.mHash = mHash;
const auto result = mLoader.load(file);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
std::unique_ptr<btCompoundShape> compound(new btCompoundShape);
compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true));
Resource::BulletShape expected;
expected.mCollisionShape.reset(compound.release());
expected.mVisualCollisionType = Resource::VisualCollisionType::Default;
EXPECT_EQ(*result, expected);
}
@ -1121,33 +1126,13 @@ namespace
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape)
{
mNiStringExtraData.mData = "MRK";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&mNiNode);
file.mHash = mHash;
const auto result = mLoader.load(file);
Resource::BulletShape expected;
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers)
{
mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker"
mNiIntegerExtraData.recType = Nif::RC_BSXFlags;
mNiTriShape.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData));
mNiTriShape.mParents.push_back(&mNiNode);
mNiTriShape.mName = "EditorMarker";
mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker"
mNiIntegerExtraData.recType = Nif::RC_BSXFlags;
mNiNode.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData));
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
@ -1162,18 +1147,14 @@ namespace
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader,
for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes)
TEST_F(TestBulletNifLoader, mrk_editor_marker_flag_disables_collision_for_markers)
{
mNiTriShape.mParents.push_back(&mNiNode);
mNiTriShape.mName = "Tri EditorMarker";
mNiStringExtraData.mData = "MRK";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode2);
mNiNode2.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
mNiNode2.recType = Nif::RC_RootCollisionNode;
mNiNode2.mParents.push_back(&mNiNode);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiNode2) };
mNiNode.recType = Nif::RC_NiNode;
mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&mNiNode);
@ -1181,13 +1162,7 @@ namespace
const auto result = mLoader.load(file);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
std::unique_ptr<btCompoundShape> compound(new btCompoundShape);
compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true));
Resource::BulletShape expected;
expected.mCollisionShape.reset(compound.release());
EXPECT_EQ(*result, expected);
}

View file

@ -108,7 +108,7 @@ osg::Group {
)");
}
std::string formatOsgNodeForShaderProperty(std::string_view shaderPrefix)
std::string formatOsgNodeForBSShaderProperty(std::string_view shaderPrefix)
{
std::ostringstream oss;
oss << R"(
@ -165,6 +165,72 @@ osg::Group {
return oss.str();
}
std::string formatOsgNodeForBSLightingShaderProperty(std::string_view shaderPrefix)
{
std::ostringstream oss;
oss << R"(
osg::Group {
UniqueID 1
DataVariance STATIC
UserDataContainer TRUE {
osg::DefaultUserDataContainer {
UniqueID 2
UDC_UserObjects 1 {
osg::StringValueObject {
UniqueID 3
Name "fileHash"
}
}
}
}
Children 1 {
osg::Group {
UniqueID 4
DataVariance STATIC
UserDataContainer TRUE {
osg::DefaultUserDataContainer {
UniqueID 5
UDC_UserObjects 3 {
osg::UIntValueObject {
UniqueID 6
Name "recIndex"
Value 4294967295
}
osg::StringValueObject {
UniqueID 7
Name "shaderPrefix"
Value ")"
<< shaderPrefix << R"("
}
osg::BoolValueObject {
UniqueID 8
Name "shaderRequired"
Value TRUE
}
}
}
}
StateSet TRUE {
osg::StateSet {
UniqueID 9
ModeList 1 {
GL_DEPTH_TEST ON
}
AttributeList 1 {
osg::Depth {
UniqueID 10
}
Value OFF
}
}
}
}
}
}
)";
return oss.str();
}
struct ShaderPrefixParams
{
unsigned int mShaderType;
@ -194,7 +260,7 @@ osg::Group {
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager);
EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix));
EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix));
}
INSTANTIATE_TEST_SUITE_P(Params, NifOsgLoaderBSShaderPrefixTest, ValuesIn(NifOsgLoaderBSShaderPrefixTest::sParams));
@ -218,11 +284,13 @@ osg::Group {
property.mTextureSet = nullptr;
property.mController = nullptr;
property.mType = GetParam().mShaderType;
property.mShaderFlags1 |= Nif::BSShaderFlags1::BSSFlag1_DepthTest;
property.mShaderFlags2 |= Nif::BSShaderFlags2::BSSFlag2_DepthWrite;
node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager);
EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix));
EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix));
}
INSTANTIATE_TEST_SUITE_P(

View file

@ -16,10 +16,10 @@ namespace ESM
struct AIData
{
unsigned short mHello; // This is the base value for greeting distance [0, 65535]
uint16_t mHello; // This is the base value for greeting distance [0, 65535]
unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100]
char mU1, mU2, mU3; // Unknown values
int mServices; // See the Services enum
int32_t mServices; // See the Services enum
void blank();
///< Set record to default state (does not touch the ID).
@ -27,8 +27,8 @@ namespace ESM
struct AIWander
{
short mDistance;
short mDuration;
int16_t mDistance;
int16_t mDuration;
unsigned char mTimeOfDay;
unsigned char mIdle[8];
unsigned char mShouldRepeat;
@ -44,7 +44,7 @@ namespace ESM
struct AITarget
{
float mX, mY, mZ;
short mDuration;
int16_t mDuration;
NAME32 mId;
unsigned char mShouldRepeat;
unsigned char mPadding;

View file

@ -196,7 +196,7 @@ namespace ESM
int count = 0;
while (esm.isNextSub("AIPK"))
{
int type;
int32_t type;
esm.getHT(type);
mPackages.emplace_back();

View file

@ -9,7 +9,7 @@ namespace ESM
struct CreatureLevListState final : public ObjectState
{
int mSpawnActorId;
int32_t mSpawnActorId;
bool mSpawn;
void load(ESMReader& esm) override;

View file

@ -47,9 +47,8 @@ namespace ESM
std::vector<int> mSummonGraveyard;
TimeStamp mTradeTime;
int mGoldPool;
int mActorId;
// int mHitAttemptActorId;
int32_t mGoldPool;
int32_t mActorId;
enum Flags
{

View file

@ -24,14 +24,14 @@ namespace ESM
Flag_Global = 4 // make available from main menu (i.e. not location specific)
};
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId;
std::string mDescription;
std::string mScriptText;
unsigned int mFlags;
uint32_t mFlags;
void load(ESMReader& esm, bool& isDeleted);
void save(ESMWriter& esm, bool isDeleted = false) const;

View file

@ -9,7 +9,7 @@ namespace ESM
struct DoorState final : public ObjectState
{
int mDoorState = 0;
int32_t mDoorState = 0;
void load(ESMReader& esm) override;
void save(ESMWriter& esm, bool inInventory = false) const override;

View file

@ -17,7 +17,7 @@ namespace ESM
static constexpr std::string_view getRecordType() { return "Filter"; }
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId;
std::string mDescription;

View file

@ -7,22 +7,25 @@
namespace ESM
{
namespace
{
constexpr uint32_t sInvalidSlot = static_cast<uint32_t>(-1);
}
void InventoryState::load(ESMReader& esm)
{
// obsolete
int index = 0;
uint32_t index = 0;
while (esm.isNextSub("IOBJ"))
{
int unused; // no longer used
esm.getHT(unused);
esm.skipHT<int32_t>();
ObjectState state;
// obsolete
if (esm.isNextSub("SLOT"))
{
int slot;
int32_t slot;
esm.getHT(slot);
mEquipmentSlots[index] = slot;
}
@ -38,9 +41,9 @@ namespace ESM
++index;
}
int itemsCount = 0;
uint32_t itemsCount = 0;
esm.getHNOT(itemsCount, "ICNT");
for (int i = 0; i < itemsCount; i++)
for (; itemsCount > 0; --itemsCount)
{
ObjectState state;
@ -62,7 +65,7 @@ namespace ESM
{
// Get its name
ESM::RefId id = esm.getRefId();
int count;
int32_t count;
std::string parentGroup;
// Then get its count
esm.getHNT(count, "COUN");
@ -91,9 +94,9 @@ namespace ESM
while (esm.isNextSub("EQUI"))
{
esm.getSubHeader();
int equipIndex;
int32_t equipIndex;
esm.getT(equipIndex);
int slot;
int32_t slot;
esm.getT(slot);
mEquipmentSlots[equipIndex] = slot;
}
@ -101,20 +104,24 @@ namespace ESM
if (esm.isNextSub("EQIP"))
{
esm.getSubHeader();
int slotsCount = 0;
uint32_t slotsCount = 0;
esm.getT(slotsCount);
for (int i = 0; i < slotsCount; i++)
for (; slotsCount > 0; --slotsCount)
{
int equipIndex;
int32_t equipIndex;
esm.getT(equipIndex);
int slot;
int32_t slot;
esm.getT(slot);
mEquipmentSlots[equipIndex] = slot;
}
}
mSelectedEnchantItem = -1;
esm.getHNOT(mSelectedEnchantItem, "SELE");
uint32_t selectedEnchantItem = sInvalidSlot;
esm.getHNOT(selectedEnchantItem, "SELE");
if (selectedEnchantItem == sInvalidSlot)
mSelectedEnchantItem.reset();
else
mSelectedEnchantItem = selectedEnchantItem;
// Old saves had restocking levelled items in a special map
// This turns items from that map into negative quantities
@ -132,7 +139,7 @@ namespace ESM
void InventoryState::save(ESMWriter& esm) const
{
int itemsCount = static_cast<int>(mItems.size());
uint32_t itemsCount = static_cast<uint32_t>(mItems.size());
if (itemsCount > 0)
{
esm.writeHNT("ICNT", itemsCount);
@ -149,34 +156,32 @@ namespace ESM
esm.writeHNString("LGRP", it->first.second);
}
for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin();
it != mPermanentMagicEffectMagnitudes.end(); ++it)
for (const auto& [id, params] : mPermanentMagicEffectMagnitudes)
{
esm.writeHNRefId("MAGI", it->first);
esm.writeHNRefId("MAGI", id);
const std::vector<std::pair<float, float>>& params = it->second;
for (std::vector<std::pair<float, float>>::const_iterator pIt = params.begin(); pIt != params.end(); ++pIt)
for (const auto& [rand, mult] : params)
{
esm.writeHNT("RAND", pIt->first);
esm.writeHNT("MULT", pIt->second);
esm.writeHNT("RAND", rand);
esm.writeHNT("MULT", mult);
}
}
int slotsCount = static_cast<int>(mEquipmentSlots.size());
uint32_t slotsCount = static_cast<uint32_t>(mEquipmentSlots.size());
if (slotsCount > 0)
{
esm.startSubRecord("EQIP");
esm.writeT(slotsCount);
for (std::map<int, int>::const_iterator it = mEquipmentSlots.begin(); it != mEquipmentSlots.end(); ++it)
for (const auto& [index, slot] : mEquipmentSlots)
{
esm.writeT(it->first);
esm.writeT(it->second);
esm.writeT(index);
esm.writeT(slot);
}
esm.endRecord("EQIP");
}
if (mSelectedEnchantItem != -1)
esm.writeHNT("SELE", mSelectedEnchantItem);
if (mSelectedEnchantItem)
esm.writeHNT("SELE", *mSelectedEnchantItem);
}
}

View file

@ -2,6 +2,7 @@
#define OPENMW_ESM_INVENTORYSTATE_H
#include <map>
#include <optional>
#include "objectstate.hpp"
#include <components/esm/refid.hpp>
@ -19,20 +20,15 @@ namespace ESM
std::vector<ObjectState> mItems;
// <Index in mItems, equipment slot>
std::map<int, int> mEquipmentSlots;
std::map<uint32_t, int32_t> mEquipmentSlots;
std::map<std::pair<ESM::RefId, std::string>, int> mLevelledItemMap;
std::map<std::pair<ESM::RefId, std::string>, int32_t> mLevelledItemMap;
typedef std::map<ESM::RefId, std::vector<std::pair<float, float>>> TEffectMagnitudes;
TEffectMagnitudes mPermanentMagicEffectMagnitudes;
std::map<ESM::RefId, std::vector<std::pair<float, float>>> mPermanentMagicEffectMagnitudes;
int mSelectedEnchantItem; // For inventories only
std::optional<uint32_t> mSelectedEnchantItem; // For inventories only
InventoryState()
: mSelectedEnchantItem(-1)
{
}
virtual ~InventoryState() {}
virtual ~InventoryState() = default;
virtual void load(ESMReader& esm);
virtual void save(ESMWriter& esm) const;

View file

@ -17,7 +17,7 @@ namespace ESM
///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum
void adjustRefNum(RefNum& refNum, const ESMReader& reader)
{
unsigned int local = (refNum.mIndex & 0xff000000) >> 24;
uint32_t local = (refNum.mIndex & 0xff000000) >> 24;
// If we have an index value that does not make sense, assume that it was an addition
// by the present plugin (but a faulty one)
@ -124,7 +124,7 @@ namespace ESM
switch (esm.retSubName().toInt())
{
case fourCC("INTV"):
int waterl;
int32_t waterl;
esm.getHT(waterl);
mWater = static_cast<float>(waterl);
mWaterInt = true;
@ -192,7 +192,7 @@ namespace ESM
{
if (mWaterInt)
{
int water = (mWater >= 0) ? (int)(mWater + 0.5) : (int)(mWater - 0.5);
int32_t water = (mWater >= 0) ? static_cast<int32_t>(mWater + 0.5) : static_cast<int32_t>(mWater - 0.5);
esm.writeHNT("INTV", water);
}
else
@ -218,13 +218,13 @@ namespace ESM
}
}
void Cell::saveTempMarker(ESMWriter& esm, int tempCount) const
void Cell::saveTempMarker(ESMWriter& esm, int32_t tempCount) const
{
if (tempCount != 0)
esm.writeHNT("NAM0", tempCount);
}
void Cell::restore(ESMReader& esm, int iCtx) const
void Cell::restore(ESMReader& esm, size_t iCtx) const
{
esm.restoreContext(mContextList.at(iCtx));
}
@ -321,7 +321,7 @@ namespace ESM
void Cell::blank()
{
mName = "";
mName.clear();
mRegion = ESM::RefId();
mWater = 0;
mWaterInt = false;

View file

@ -34,7 +34,7 @@ namespace ESM
RefNum mRefNum;
// Coordinates of target exterior cell
int mTarget[2];
int32_t mTarget[2];
// The content file format does not support moving objects to an interior cell.
// The save game format does support moving to interior cells, but uses a different mechanism
@ -153,13 +153,13 @@ namespace ESM
ESMReader& esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references
void save(ESMWriter& esm, bool isDeleted = false) const;
void saveTempMarker(ESMWriter& esm, int tempCount) const;
void saveTempMarker(ESMWriter& esm, int32_t tempCount) const;
bool isExterior() const { return !(mData.mFlags & Interior); }
int getGridX() const { return mData.mX; }
int32_t getGridX() const { return mData.mX; }
int getGridY() const { return mData.mY; }
int32_t getGridY() const { return mData.mY; }
bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); }
@ -172,7 +172,7 @@ namespace ESM
// somewhere other than the file system, you need to pre-open the
// ESMReader, and the filename must match the stored filename
// exactly.
void restore(ESMReader& esm, int iCtx) const;
void restore(ESMReader& esm, size_t iCtx) const;
std::string getDescription() const;
///< Return a short string describing the cell (mostly used for debugging/logging purpose)

View file

@ -19,7 +19,7 @@ namespace ESM
struct ContItem
{
int mCount{ 0 };
int32_t mCount{ 0 };
ESM::RefId mItem;
};
@ -48,12 +48,12 @@ namespace ESM
Unknown = 8
};
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId, mScript;
std::string mName, mModel;
float mWeight; // Not sure, might be max total weight allowed?
int mFlags;
int32_t mFlags;
InventoryList mInventory;
void load(ESMReader& esm, bool& isDeleted);

View file

@ -25,7 +25,7 @@ namespace ESM
/// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "Global"; }
unsigned int mRecordFlags;
uint32_t mRecordFlags;
ESM::RefId mId;
Variant mValue;

View file

@ -26,7 +26,7 @@ namespace ESM
/// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "GameSetting"; }
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId;
Variant mValue;

View file

@ -30,7 +30,7 @@ namespace ESM
break;
case fourCC("INDX"):
{
int length = 0;
uint32_t length = 0;
esm.getHT(length);
mList.resize(length);
@ -87,12 +87,12 @@ namespace ESM
esm.writeHNT("DATA", mFlags);
esm.writeHNT("NNAM", mChanceNone);
esm.writeHNT<int>("INDX", mList.size());
esm.writeHNT<uint32_t>("INDX", mList.size());
for (std::vector<LevelItem>::const_iterator it = mList.begin(); it != mList.end(); ++it)
for (const auto& item : mList)
{
esm.writeHNCRefId(recName, it->mId);
esm.writeHNT("INTV", it->mLevel);
esm.writeHNCRefId(recName, item.mId);
esm.writeHNT("INTV", item.mLevel);
}
}

View file

@ -24,15 +24,15 @@ namespace ESM
struct LevelledListBase
{
int mFlags;
int32_t mFlags;
unsigned char mChanceNone; // Chance that none are selected (0-100)
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId;
struct LevelItem
{
RefId mId;
short mLevel;
uint16_t mLevel;
};
std::vector<LevelItem> mList;

View file

@ -29,7 +29,7 @@ namespace ESM
// mId is merely a user friendly name for the texture in the editor.
std::string mTexture;
RefId mId;
int mIndex;
int32_t mIndex;
void load(ESMReader& esm, bool& isDeleted);
void save(ESMWriter& esm, bool isDeleted = false) const;

View file

@ -36,7 +36,7 @@ namespace ESM
esm.getSubNameIs("MEDT");
esm.getSubHeader();
int school;
int32_t school;
esm.getT(school);
mData.mSchool = MagicSchool::indexToSkillRefId(school);
esm.getT(mData.mBaseCost);

View file

@ -25,7 +25,7 @@ namespace ESM
/// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "MagicEffect"; }
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId;
enum Flags
@ -74,9 +74,9 @@ namespace ESM
{
RefId mSchool; // Skill id
float mBaseCost;
int mFlags;
int32_t mFlags;
// Glow color for enchanted items with this effect
int mRed, mGreen, mBlue;
int32_t mRed, mGreen, mBlue;
float mUnknown1; // Called "Size X" in CS
float mSpeed; // Speed of fired projectile
@ -107,7 +107,7 @@ namespace ESM
// there. They can be redefined in mods by setting the name in GMST
// sEffectSummonCreature04/05 creature id in
// sMagicCreature04ID/05ID.
int mIndex;
int32_t mIndex;
void load(ESMReader& esm, bool& isDeleted);
void save(ESMWriter& esm, bool isDeleted = false) const;

View file

@ -82,7 +82,7 @@ namespace ESM
break;
case fourCC("FLAG"):
hasFlags = true;
int flags;
int32_t flags;
esm.getHT(flags);
mFlags = flags & 0xFF;
mBloodType = ((flags >> 8) & 0xFF) >> 2;

View file

@ -79,28 +79,28 @@ namespace ESM
struct NPDTstruct52
{
short mLevel;
int16_t mLevel;
unsigned char mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck;
// mSkill can grow up to 200, it must be unsigned
std::array<unsigned char, Skill::Length> mSkills;
char mUnknown1;
unsigned short mHealth, mMana, mFatigue;
uint16_t mHealth, mMana, mFatigue;
unsigned char mDisposition, mReputation, mRank;
char mUnknown2;
int mGold;
int32_t mGold;
}; // 52 bytes
// Structure for autocalculated characters.
// This is only used for load and save operations.
struct NPDTstruct12
{
short mLevel;
int16_t mLevel;
// see above
unsigned char mDisposition, mReputation, mRank;
char mUnknown1, mUnknown2, mUnknown3;
int mGold;
int32_t mGold;
}; // 12 bytes
#pragma pack(pop)
@ -111,7 +111,7 @@ namespace ESM
int getFactionRank() const; /// wrapper for mNpdt*, -1 = no rank
int mBloodType;
int32_t mBloodType;
unsigned char mFlags;
InventoryList mInventory;
@ -125,7 +125,7 @@ namespace ESM
AIPackageList mAiPackage;
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId, mRace, mClass, mFaction, mScript;
std::string mModel, mName;

View file

@ -27,13 +27,13 @@ namespace ESM
struct SkillBonus
{
int mSkill; // SkillEnum
int mBonus;
int32_t mSkill; // SkillEnum
int32_t mBonus;
};
struct MaleFemale
{
int mMale, mFemale;
int32_t mMale, mFemale;
int getValue(bool male) const;
};
@ -63,13 +63,13 @@ namespace ESM
// as 'height' times 128. This has not been tested yet.
MaleFemaleF mHeight, mWeight;
int mFlags; // 0x1 - playable, 0x2 - beast race
int32_t mFlags; // 0x1 - playable, 0x2 - beast race
}; // Size = 140 bytes
RADTstruct mData;
unsigned int mRecordFlags;
uint32_t mRecordFlags;
std::string mName, mDescription;
RefId mId;
SpellList mPowers;

View file

@ -36,9 +36,9 @@ namespace ESM
};
// Type
int mType;
int32_t mType;
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId, mCreature, mSound;
void load(ESMReader& esm, bool& isDeleted);

View file

@ -28,7 +28,7 @@ namespace ESM
static std::string_view getRecordType() { return "StartScript"; }
std::string mData;
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId;
// Load a record and add it to the list

View file

@ -31,7 +31,7 @@ namespace ESM
/// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "Static"; }
unsigned int mRecordFlags;
uint32_t mRecordFlags;
RefId mId;
std::string mModel;

View file

@ -20,11 +20,11 @@ namespace ESM
versions are 1.2 and 1.3. These correspond to:
1.2 = 0x3f99999a and 1.3 = 0x3fa66666
*/
unsigned int version;
int type; // 0=esp, 1=esm, 32=ess (unused)
uint32_t version;
int32_t type; // 0=esp, 1=esm, 32=ess (unused)
std::string author; // Author's name
std::string desc; // File description
int records; // Number of records
int32_t records; // Number of records
};
struct GMDT

View file

@ -20,8 +20,8 @@ namespace ESM
{
while (esm.isNextSub("EFID"))
{
int id;
std::pair<int, float> params;
int32_t id;
std::pair<int32_t, float> params;
esm.getHT(id);
esm.getHNT(params.first, "BASE");
if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion)

View file

@ -16,7 +16,7 @@ namespace ESM
struct MagicEffects
{
// <Effect Id, Base value, Modifier>
std::map<int, std::pair<int, float>> mEffects;
std::map<int32_t, std::pair<int32_t, float>> mEffects;
void load(ESMReader& esm);
void save(ESMWriter& esm) const;
@ -24,16 +24,16 @@ namespace ESM
struct SummonKey
{
SummonKey(int effectId, const ESM::RefId& sourceId, int index)
SummonKey(int32_t effectId, const ESM::RefId& sourceId, int32_t index)
: mEffectId(effectId)
, mSourceId(sourceId)
, mEffectIndex(index)
{
}
int mEffectId;
int32_t mEffectId;
ESM::RefId mSourceId;
int mEffectIndex;
int32_t mEffectIndex;
};
inline auto makeTupleRef(const SummonKey& value) noexcept

View file

@ -21,7 +21,7 @@ namespace ESM
Faction faction;
int expelled = 0;
int32_t expelled = 0;
esm.getHNOT(expelled, "FAEX");
if (expelled)
@ -75,7 +75,7 @@ namespace ESM
esm.getHNOT(hasWerewolfAttributes, "HWAT");
if (hasWerewolfAttributes)
{
StatState<int> dummy;
StatState<int32_t> dummy;
for (int i = 0; i < ESM::Attribute::Length; ++i)
dummy.load(esm, intFallback);
mWerewolfDeprecatedData = true;
@ -130,21 +130,21 @@ namespace ESM
void NpcStats::save(ESMWriter& esm) const
{
for (auto iter(mFactions.begin()); iter != mFactions.end(); ++iter)
for (const auto& [id, faction] : mFactions)
{
esm.writeHNRefId("FACT", iter->first);
esm.writeHNRefId("FACT", id);
if (iter->second.mExpelled)
if (faction.mExpelled)
{
int expelled = 1;
int32_t expelled = 1;
esm.writeHNT("FAEX", expelled);
}
if (iter->second.mRank >= 0)
esm.writeHNT("FARA", iter->second.mRank);
if (faction.mRank >= 0)
esm.writeHNT("FARA", faction.mRank);
if (iter->second.mReputation)
esm.writeHNT("FARE", iter->second.mReputation);
if (faction.mReputation)
esm.writeHNT("FARE", faction.mReputation);
}
if (mDisposition)
@ -169,7 +169,7 @@ namespace ESM
esm.writeHNT("LPRO", mLevelProgress);
bool saveSkillIncreases = false;
for (int increase : mSkillIncrease)
for (int32_t increase : mSkillIncrease)
{
if (increase != 0)
{
@ -183,8 +183,8 @@ namespace ESM
if (mSpecIncreases[0] != 0 || mSpecIncreases[1] != 0 || mSpecIncreases[2] != 0)
esm.writeHNT("SPEC", mSpecIncreases);
for (auto iter(mUsedIds.begin()); iter != mUsedIds.end(); ++iter)
esm.writeHNRefId("USED", *iter);
for (const RefId& id : mUsedIds)
esm.writeHNRefId("USED", id);
if (mTimeToStartDrowning)
esm.writeHNT("DRTI", mTimeToStartDrowning);

View file

@ -23,8 +23,8 @@ namespace ESM
struct Faction
{
bool mExpelled;
int mRank;
int mReputation;
int32_t mRank;
int32_t mReputation;
Faction();
};
@ -33,18 +33,18 @@ namespace ESM
bool mWerewolfDeprecatedData;
std::map<ESM::RefId, Faction> mFactions; // lower case IDs
int mDisposition;
std::map<ESM::RefId, Faction> mFactions;
int32_t mDisposition;
std::array<StatState<float>, ESM::Skill::Length> mSkills;
int mBounty;
int mReputation;
int mWerewolfKills;
int mLevelProgress;
std::array<int, ESM::Attribute::Length> mSkillIncrease;
std::array<int, 3> mSpecIncreases;
std::vector<ESM::RefId> mUsedIds; // lower case IDs
int32_t mBounty;
int32_t mReputation;
int32_t mWerewolfKills;
int32_t mLevelProgress;
std::array<int32_t, ESM::Attribute::Length> mSkillIncrease;
std::array<int32_t, 3> mSpecIncreases;
std::vector<ESM::RefId> mUsedIds;
float mTimeToStartDrowning;
int mCrimeId;
int32_t mCrimeId;
/// Initialize to default state
void blank();

View file

@ -49,7 +49,7 @@ namespace ESM
esm.getHNOT(mFlags, "FLAG");
// obsolete
int unused;
int32_t unused;
esm.getHNOT(unused, "LTIM");
mAnimationState.load(esm);
@ -179,6 +179,6 @@ namespace ESM
throw std::logic_error(error.str());
}
ObjectState::~ObjectState() {}
ObjectState::~ObjectState() = default;
}

View file

@ -32,9 +32,9 @@ namespace ESM
Locals mLocals;
LuaScripts mLuaScripts;
unsigned char mEnabled;
int mCount;
int32_t mCount;
Position mPosition;
unsigned int mFlags;
uint32_t mFlags;
// Is there any class-specific state following the ObjectState
bool mHasCustomState;

View file

@ -27,8 +27,8 @@ namespace ESM
RefId mMarkedCell;
ESM::RefId mBirthsign;
int mCurrentCrimeId;
int mPaidCrimeId;
int32_t mCurrentCrimeId;
int32_t mPaidCrimeId;
float mSaveAttributes[Attribute::Length];
float mSaveSkills[Skill::Length];

View file

@ -23,7 +23,7 @@ namespace ESM
Vector3 mPosition;
Quaternion mOrientation;
int mActorId;
int32_t mActorId;
void load(ESMReader& esm);
void save(ESMWriter& esm) const;

View file

@ -14,7 +14,7 @@ namespace ESM
struct QuestState
{
ESM::RefId mTopic; // lower case id
int mState;
int32_t mState;
unsigned char mFinished;
void load(ESMReader& esm);

View file

@ -20,7 +20,7 @@ namespace ESM
std::vector<std::string> mContentFiles;
std::string mPlayerName;
int mPlayerLevel;
int32_t mPlayerLevel;
// ID of class
ESM::RefId mPlayerClassId;
@ -34,7 +34,7 @@ namespace ESM
std::string mDescription;
std::vector<char> mScreenshot; // raw jpg-encoded data
int mCurrentDay = 0;
int32_t mCurrentDay = 0;
float mCurrentHealth = 0;
float mMaximumHealth = 0;

View file

@ -21,19 +21,19 @@ namespace ESM
// We changed stats values from integers to floats; ensure backwards compatibility
if (intFallback)
{
int base = 0;
int32_t base = 0;
esm.getHNT(base, "STBA");
mBase = static_cast<T>(base);
int mod = 0;
int32_t mod = 0;
esm.getHNOT(mod, "STMO");
mMod = static_cast<T>(mod);
int current = 0;
int32_t current = 0;
esm.getHNOT(current, "STCU");
mCurrent = static_cast<T>(current);
int oldDamage = 0;
int32_t oldDamage = 0;
esm.getHNOT(oldDamage, "STDA");
mDamage = static_cast<float>(oldDamage);
}

View file

@ -17,7 +17,7 @@ namespace ESM
template <typename T, bool orDefault = false>
struct GetValue
{
constexpr T operator()(int value) const { return static_cast<T>(value); }
constexpr T operator()(int32_t value) const { return static_cast<T>(value); }
constexpr T operator()(float value) const { return static_cast<T>(value); }
@ -41,7 +41,7 @@ namespace ESM
{
}
void operator()(int& value) const { value = static_cast<int>(mValue); }
void operator()(int32_t& value) const { value = static_cast<int32_t>(mValue); }
void operator()(float& value) const { value = static_cast<float>(mValue); }
@ -58,9 +58,9 @@ namespace ESM
return std::get<std::string>(mData);
}
int Variant::getInteger() const
int32_t Variant::getInteger() const
{
return std::visit(GetValue<int>{}, mData);
return std::visit(GetValue<int32_t>{}, mData);
}
float Variant::getFloat() const
@ -194,17 +194,17 @@ namespace ESM
case VT_Short:
stream << "variant short: " << std::get<int>(mData);
stream << "variant short: " << std::get<int32_t>(mData);
break;
case VT_Int:
stream << "variant int: " << std::get<int>(mData);
stream << "variant int: " << std::get<int32_t>(mData);
break;
case VT_Long:
stream << "variant long: " << std::get<int>(mData);
stream << "variant long: " << std::get<int32_t>(mData);
break;
case VT_Float:
@ -259,7 +259,7 @@ namespace ESM
std::get<std::string>(mData) = std::move(value);
}
void Variant::setInteger(int value)
void Variant::setInteger(int32_t value)
{
std::visit(SetValue(value), mData);
}

View file

@ -25,7 +25,7 @@ namespace ESM
class Variant
{
VarType mType;
std::variant<std::monostate, int, float, std::string> mData;
std::variant<std::monostate, int32_t, float, std::string> mData;
public:
enum Format
@ -54,7 +54,7 @@ namespace ESM
{
}
explicit Variant(int value)
explicit Variant(int32_t value)
: mType(VT_Long)
, mData(value)
{
@ -71,7 +71,7 @@ namespace ESM
const std::string& getString() const;
///< Will throw an exception, if value can not be represented as a string.
int getInteger() const;
int32_t getInteger() const;
///< Will throw an exception, if value can not be represented as an integer (implicit
/// casting of float values is permitted).
@ -93,7 +93,7 @@ namespace ESM
void setString(std::string&& value);
///< Will throw an exception, if type is not compatible with string.
void setInteger(int value);
void setInteger(int32_t value);
///< Will throw an exception, if type is not compatible with integer.
void setFloat(float value);

View file

@ -46,7 +46,7 @@ namespace ESM
esm.writeHNString("STRV", in);
}
void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out)
void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int32_t& out)
{
if (type != VT_Short && type != VT_Long && type != VT_Int)
throw std::logic_error("not an integer type");
@ -60,9 +60,9 @@ namespace ESM
if (std::isnan(value))
out = 0;
else
out = static_cast<short>(value);
out = static_cast<int16_t>(value);
else if (type == VT_Long)
out = static_cast<int>(value);
out = static_cast<int32_t>(value);
else
esm.fail("unsupported global variable integer type");
}
@ -82,7 +82,7 @@ namespace ESM
{
if (type == VT_Short)
{
short value;
int16_t value;
esm.getHT(value);
out = value;
}
@ -95,7 +95,7 @@ namespace ESM
}
}
void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in)
void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int32_t in)
{
if (type != VT_Short && type != VT_Long && type != VT_Int)
throw std::logic_error("not an integer type");
@ -126,7 +126,7 @@ namespace ESM
else if (format == Variant::Format_Local)
{
if (type == VT_Short)
esm.writeHNT("STTV", static_cast<short>(in));
esm.writeHNT("STTV", static_cast<int16_t>(in));
else if (type == VT_Int)
esm.writeHNT("INTV", in);
else

View file

@ -12,13 +12,13 @@ namespace ESM
void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, float& value);
void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int& value);
void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int32_t& value);
void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, const std::string& value);
void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, float value);
void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int value);
void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int32_t value);
struct ReadESMVariantValue
{

View file

@ -76,6 +76,9 @@ void ESM4::LandTexture::load(ESM4::Reader& reader)
case ESM4::SUB_MNAM:
reader.getFormId(mMaterial);
break; // TES5, FO4
case ESM4::SUB_INAM:
reader.get(mMaterialFlags);
break; // SSE
default:
throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId));
}

View file

@ -63,6 +63,17 @@ namespace ESM4
// ----------------------
// ------ SSE -----------
enum MaterialFlags
{
Flag_IsSnow = 0x1,
};
std::uint32_t mMaterialFlags;
// ----------------------
void load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const;

View file

@ -116,9 +116,11 @@ void ESM4::Npc::load(ESM4::Reader& reader)
{
switch (subHdr.dataSize)
{
case 20: // FO4
mIsFO4 = true;
[[fallthrough]];
case 16: // TES4
case 24: // FO3/FNV, TES5
case 20: // FO4
reader.get(&mBaseConfig, subHdr.dataSize);
break;
default:

View file

@ -78,6 +78,7 @@ namespace ESM4
FO3_NoRotateHead = 0x40000000
};
// In FO4 flags seem to be the same.
enum ACBS_TES5
{
TES5_Female = 0x00000001,
@ -101,27 +102,32 @@ namespace ESM4
TES5_Invulnerable = 0x80000000
};
// All FO3+ games.
enum Template_Flags
{
TES5_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight,
// voice type, death item; Sounds tab; Animation tab; Character Gen tabs
TES5_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina,
// speed, bleedout, class
TES5_UseFactions = 0x0004, // both factions and assigned crime faction
TES5_UseSpellList = 0x0008, // both spells and perks
TES5_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and
// gift filter
TES5_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab;
// rest of tab controlled by Def Pack List
TES5_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected,
// Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter
TES5_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item
// -- but not death item
TES5_UseScript = 0x0200,
TES5_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab)
TES5_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race,
// events, and data)
TES5_UseKeywords = 0x1000
Template_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight,
// voice type, death item; Sounds tab; Animation tab; Character Gen tabs
Template_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina,
// speed, bleedout, class
Template_UseFactions = 0x0004, // both factions and assigned crime faction
Template_UseSpellList = 0x0008, // both spells and perks
Template_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and
// gift filter
Template_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab;
// rest of tab controlled by Def Pack List
Template_UseModel = 0x0040, // FO3, FONV; probably not used in TES5+
Template_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected,
// Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter
Template_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item,
// but not death item
Template_UseScript = 0x0200,
// The following flags were added in TES5+:
Template_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab)
Template_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race,
// events, and data)
Template_UseKeywords = 0x1000
};
#pragma pack(push, 1)
@ -172,6 +178,7 @@ namespace ESM4
bool mIsTES4;
bool mIsFONV;
bool mIsFO4 = false;
std::string mEditorId;
std::string mFullName;

View file

@ -110,7 +110,7 @@ namespace ESM4
ESM::FormId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
bool mIsTES5;
bool mIsTES5 = false;
std::string mEditorId;
std::string mFullName;

View file

@ -12,7 +12,6 @@
#include <osg/StateSet>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/clearcolor.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/settings/values.hpp>
#include <components/stereo/multiview.hpp>
@ -190,6 +189,11 @@ mat4 omw_InvProjectionMatrix()
#endif
}
vec3 omw_GetNormalsWorldSpace(vec2 uv)
{
return (vec4(omw_GetNormals(uv), 0.0) * omw.viewMatrix).rgb;
}
vec3 omw_GetWorldPosFromUV(vec2 uv)
{
float depth = omw_GetDepth(uv);
@ -321,9 +325,6 @@ float omw_EstimateFogCoverageFromUV(vec2 uv)
if (mBlendEq)
stateSet->setAttributeAndModes(new osg::BlendEquation(mBlendEq.value()));
if (mClearColor)
stateSet->setAttributeAndModes(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT));
}
void Pass::dirty()
@ -339,7 +340,7 @@ float omw_EstimateFogCoverageFromUV(vec2 uv)
if (mCompiled)
return;
mLegacyGLSL = technique.getGLSLVersion() != 330;
mLegacyGLSL = technique.getGLSLVersion() < 330;
if (mType == Type::Pixel)
{

View file

@ -72,7 +72,6 @@ namespace fx
std::array<std::string, 3> mRenderTargets;
std::string mTarget;
std::optional<osg::Vec4f> mClearColor;
std::optional<osg::BlendFunc::BlendFuncMode> mBlendSource;
std::optional<osg::BlendFunc::BlendFuncMode> mBlendDest;

View file

@ -279,6 +279,7 @@ namespace fx
rt.mTarget->setSourceType(GL_UNSIGNED_BYTE);
rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
rt.mTarget->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
rt.mTarget->setName(std::string(mBlockName));
while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
{
@ -312,6 +313,8 @@ namespace fx
rt.mTarget->setSourceFormat(parseSourceFormat());
else if (key == "mipmaps")
rt.mMipMap = parseBool();
else if (key == "clear_color")
rt.mClearColor = parseVec<osg::Vec4f, Lexer::Vec4>();
else
error(Misc::StringUtils::format("unexpected key '%s'", std::string(key)));
@ -797,9 +800,6 @@ namespace fx
if (!pass)
pass = std::make_shared<fx::Pass>();
bool clear = true;
osg::Vec4f clearColor = { 1, 1, 1, 1 };
while (!isNext<Lexer::Eof>())
{
expect<Lexer::Literal>("invalid key in block header");
@ -843,10 +843,6 @@ namespace fx
if (blendEq != osg::BlendEquation::FUNC_ADD)
pass->mBlendEq = blendEq;
}
else if (key == "clear")
clear = parseBool();
else if (key == "clear_color")
clearColor = parseVec<osg::Vec4f, Lexer::Vec4>();
else
error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key)));
@ -864,9 +860,6 @@ namespace fx
return;
}
if (clear)
pass->mClearColor = clearColor;
error("malformed block header");
}

View file

@ -54,10 +54,14 @@ namespace fx
osg::ref_ptr<osg::FrameBufferObject> mRenderTarget;
osg::ref_ptr<osg::Texture2D> mRenderTexture;
bool mResolve = false;
Types::SizeProxy mSize;
bool mMipMap;
SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY)
: mStateSet(new osg::StateSet(*other.mStateSet, copyOp))
, mResolve(other.mResolve)
, mSize(other.mSize)
, mMipMap(other.mMipMap)
{
if (other.mRenderTarget)
mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp);

View file

@ -29,6 +29,16 @@ namespace fx
std::optional<int> mWidth;
std::optional<int> mHeight;
SizeProxy() = default;
SizeProxy(const SizeProxy& other)
: mWidthRatio(other.mWidthRatio)
, mHeightRatio(other.mHeightRatio)
, mWidth(other.mWidth)
, mHeight(other.mHeight)
{
}
std::tuple<int, int> get(int width, int height) const
{
int scaledWidth = width;
@ -53,6 +63,17 @@ namespace fx
osg::ref_ptr<osg::Texture2D> mTarget = new osg::Texture2D;
SizeProxy mSize;
bool mMipMap = false;
osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0);
RenderTarget() = default;
RenderTarget(const RenderTarget& other)
: mTarget(other.mTarget)
, mSize(other.mSize)
, mMipMap(other.mMipMap)
, mClearColor(other.mClearColor)
{
}
};
template <class T>

View file

@ -8,9 +8,7 @@ namespace LuaUi
{
void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize)
{
mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight());
mAlign = MyGUI::Align::Stretch;
MyGUI::TileRect::_setAlign(_oldsize);
mCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight());
mTileSize = mSetTileSize;
// zero tilesize stands for not tiling
@ -25,6 +23,8 @@ namespace LuaUi
mTileSize.width = 1e7;
if (mTileSize.height <= 0)
mTileSize.height = 1e7;
MyGUI::TileRect::_updateView();
}
void LuaImage::initialize()
@ -55,13 +55,13 @@ namespace LuaUi
if (texture != nullptr)
textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight());
mTileRect->updateSize(MyGUI::IntSize(tileH ? textureSize.width : 0, tileV ? textureSize.height : 0));
setImageTile(textureSize);
if (atlasCoord.width == 0)
atlasCoord.width = textureSize.width;
if (atlasCoord.height == 0)
atlasCoord.height = textureSize.height;
mTileRect->updateSize(MyGUI::IntSize(tileH ? atlasCoord.width : 0, tileV ? atlasCoord.height : 0));
setImageTile(atlasCoord.size());
setImageCoord(atlasCoord);
setColour(propertyValue("color", MyGUI::Colour(1, 1, 1, 1)));

View file

@ -8,6 +8,7 @@ namespace LuaUi
{
mEditBox = createWidget<MyGUI::EditBox>("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default);
mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange);
mEditBox->setMaxTextLength(std::numeric_limits<std::size_t>::max());
registerEvents(mEditBox);
WidgetExtension::initialize();
}

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