1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-19 08:41:35 +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 .idea
cmake-build-* cmake-build-*
files/windows/*.aps files/windows/*.aps
.cache/clangd
## qt-creator ## qt-creator
CMakeLists.txt.user* CMakeLists.txt.user*
.vs .vs

View file

@ -14,6 +14,7 @@
Bug #4754: Stack of ammunition cannot be equipped partially Bug #4754: Stack of ammunition cannot be equipped partially
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses 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 #5129: Stuttering animation on Centurion Archer
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place 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 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 #5977: Fatigueless NPCs' corpse underwater changes animation on game load
Bug #6025: Subrecords cannot overlap records Bug #6025: Subrecords cannot overlap records
Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex 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 #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 #6313: Followers with high Fight can turn hostile
Bug #6427: Enemy health bar disappears before damaging effect ends 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 #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 #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 #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 #7413: Generated wilderness cells don't spawn fish
Bug #7415: Unbreakable lock discrepancies Bug #7415: Unbreakable lock discrepancies
Bug #7428: AutoCalc flag is not used to calculate enchantment costs 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 #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 #7642: Items in repair and recharge menus aren't sorted alphabetically
Bug #7647: NPC walk cycle bugs after greeting player 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 #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 #3537: Shader-based water ripples
Feature #5492: Let rain and snow collide with statics Feature #5492: Let rain and snow collide with statics
Feature #6149: Dehardcode Lua API_REVISION Feature #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts Feature #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources
Feature #6447: Add LOD support to Object Paging Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6 Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds Feature #6556: Lua API for sounds

View file

@ -86,7 +86,7 @@ declare -rA GROUPED_DEPS=(
libswresample3 libswresample3
libswscale5 libswscale5
libtinyxml2.6.2v5 libtinyxml2.6.2v5
libyaml-cpp0.7 libyaml-cpp0.8
python3-pip python3-pip
xvfb 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/openmw-daily
add-apt-repository -y ppa:openmw/staging 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-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 std::array<std::string_view, 10> weathers
= { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" }; = { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" };
for (size_t i = 0; i < weathers.size(); ++i) 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; std::cout << " Map Color: " << mData.mMapColor << std::endl;
if (!mData.mSleepList.empty()) if (!mData.mSleepList.empty())
std::cout << " Sleep List: " << mData.mSleepList << std::endl; std::cout << " Sleep List: " << mData.mSleepList << std::endl;

View file

@ -9,15 +9,14 @@ namespace ESSImport
void convertInventory(const Inventory& inventory, ESM::InventoryState& state) void convertInventory(const Inventory& inventory, ESM::InventoryState& state)
{ {
int index = 0; uint32_t index = 0;
for (const auto& item : inventory.mItems) for (const auto& item : inventory.mItems)
{ {
ESM::ObjectState objstate; ESM::ObjectState objstate;
objstate.blank(); objstate.blank();
objstate.mRef = item; objstate.mRef = item;
objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId); objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId);
objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile objstate.mCount = item.mCount;
// openmw handles them differently, so no need to set any flags
state.mItems.push_back(objstate); state.mItems.push_back(objstate);
if (item.mRelativeEquipmentSlot != -1) if (item.mRelativeEquipmentSlot != -1)
// Note we should really write the absolute slot here, which we do not know about // 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/files/qtconversion.hpp>
#include <components/misc/strings/conversion.hpp> #include <components/misc/strings/conversion.hpp>
#include <components/navmeshtool/protocol.hpp> #include <components/navmeshtool/protocol.hpp>
#include <components/settings/settings.hpp> #include <components/settings/values.hpp>
#include <components/vfs/bsaarchive.hpp> #include <components/vfs/bsaarchive.hpp>
#include "utils/profilescombobox.hpp" #include "utils/profilescombobox.hpp"
@ -123,7 +123,7 @@ namespace Launcher
int getMaxNavMeshDbFileSizeMiB() 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) 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) void Launcher::DataFilesPage::saveSettings(const QString& profile)
{ {
if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB()) Settings::navigator().mMaxNavmeshdbFileSize.set(
Settings::Manager::setUInt64( static_cast<std::uint64_t>(std::max(0, ui.navMeshMaxSizeSpinBox->value())) * 1024 * 1024);
"max navmeshdb file size", "Navigator", static_cast<std::uint64_t>(std::max(0, value)) * 1024 * 1024);
QString profileName = profile; QString profileName = profile;

View file

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

View file

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

View file

@ -318,9 +318,14 @@ bool OMW::Engine::frame(float frametime)
mViewer->eventTraversal(); mViewer->eventTraversal();
mViewer->updateTraversal(); mViewer->updateTraversal();
// update GUI by world data
{ {
ScopedProfile<UserStatsType::WindowManager> profile(frameStart, frameNumber, *timer, *stats); 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 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) 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) for (const auto* rec : recs)
if (rec->mIsTES4 || rec->mIsFONV || !(rec->mBaseConfig.tes5.templateFlags & flag)) {
if (rec->mIsTES4)
return rec; 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; return nullptr;
} }
@ -75,8 +90,8 @@ namespace MWClass
const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore();
auto npcRecs = withBaseTemplates<ESM4::LevelledNpc, ESM4::Npc>(ptr.get<ESM4::Npc>()->mBase); auto npcRecs = withBaseTemplates<ESM4::LevelledNpc, ESM4::Npc>(ptr.get<ESM4::Npc>()->mBase);
data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseTraits); data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits);
data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseBaseData); data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData);
if (!data->mTraits) if (!data->mTraits)
throw std::runtime_error("ESM4 NPC traits not found"); 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; data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female;
else if (data->mTraits->mIsFONV) else if (data->mTraits->mIsFONV)
data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; 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 else
data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; 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) 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); std::unique_ptr<MWWorld::Action> action = ptr.getClass().use(ptr, force);
action->execute(player); 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()) if (isVisible())
{ {
mItemView->update(); mItemView->update();
@ -581,27 +595,21 @@ namespace MWGui
} }
// Handles partial equipping // 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) if (!slots.first.empty() && slots.second)
{ {
int equippedStackableCount = 0;
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator slotIt = invStore.getSlot(slots.first.front()); 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()) if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId())
equippedStackableCount = slotIt->getRefData().getCount(); mEquippedStackableCount = slotIt->getRefData().getCount();
else
useItem(ptr); mEquippedStackableCount = 0;
int unequipCount = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - equippedStackableCount;
if (unequipCount > 0)
{
invStore.unequipItemQuantity(ptr, unequipCount);
updateItemView();
}
} }
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 // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1
// item // item

View file

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

View file

@ -183,6 +183,10 @@ namespace MWGui
return switchFocus(D_Down, false); return switchFocus(D_Down, false);
case MyGUI::KeyCode::Tab: case MyGUI::KeyCode::Tab:
return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); 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::Return:
case MyGUI::KeyCode::NumpadEnter: case MyGUI::KeyCode::NumpadEnter:
case MyGUI::KeyCode::Space: case MyGUI::KeyCode::Space:

View file

@ -413,8 +413,8 @@ namespace MWGui
text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n"; text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n";
if (mCurrentSlot->mProfile.mMaximumHealth > 0) if (mCurrentSlot->mProfile.mMaximumHealth > 0)
text << std::fixed << std::setprecision(0) << "#{sHealth} " << mCurrentSlot->mProfile.mCurrentHealth << "/" text << "#{sHealth} " << static_cast<int>(mCurrentSlot->mProfile.mCurrentHealth) << "/"
<< mCurrentSlot->mProfile.mMaximumHealth << "\n"; << static_cast<int>(mCurrentSlot->mProfile.mMaximumHealth) << "\n";
text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n";
text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCellName << "}\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 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::Attribute* attribute = store.get<ESM::Attribute>().search(mEffectParams.mAttribute);
const ESM::Skill* skill = store.get<ESM::Skill>().search(mEffectParams.mSkill); const ESM::Skill* skill = store.get<ESM::Skill>().search(mEffectParams.mSkill);
assert(magicEffect);
auto windowManager = MWBase::Environment::get().getWindowManager(); auto windowManager = MWBase::Environment::get().getWindowManager();
std::string_view pt = windowManager->getGameSettingString("spoint", {}); 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 settingSection = tag.substr(0, comma_pos);
std::string_view settingTag = tag.substr(comma_pos + 1, tag.length()); 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)) else if (tag.starts_with(tokenToFind))
{ {
@ -1115,7 +1115,7 @@ namespace MWGui
else else
{ {
std::vector<std::string> split; std::vector<std::string> split;
Misc::StringUtils::split(std::string{ tag }, split, ":"); Misc::StringUtils::split(tag, split, ":");
l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager(); l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager();

View file

@ -344,6 +344,12 @@ namespace MWLua
playerScripts->uiModeChanged(argId, false); 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) void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{ {
mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. 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) }); mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) });
} }
void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override;
{
mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force });
}
void exteriorCreated(MWWorld::CellStore& cell) override void exteriorCreated(MWWorld::CellStore& cell) override
{ {
mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell });

View file

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

View file

@ -457,6 +457,9 @@ namespace MWLua
return nullptr; return nullptr;
return &*rec.mSchool; 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"); auto schoolT = context.mLua->sol().new_usertype<ESM::MagicSchool>("MagicSchool");
schoolT[sol::meta_function::to_string] schoolT[sol::meta_function::to_string]

View file

@ -395,6 +395,11 @@ namespace MWLua
return dist <= actorsProcessingRange; 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 { actor["getEncumbrance"] = [](const Object& actor) -> float {
const MWWorld::Ptr ptr = actor.ptr(); const MWWorld::Ptr ptr = actor.ptr();
return ptr.getClass().getEncumbrance(ptr); return ptr.getClass().getEncumbrance(ptr);

View file

@ -39,6 +39,6 @@ namespace MWMechanics
{ {
const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects();
return (magicEffects.getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude() > 0) 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); osg::Vec3f movementFromAnimation
if (duration > 0.0f) = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration);
moved /= duration;
else
moved = osg::Vec3f(0.f, 0.f, 0.f);
moved.x() *= scale; if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying())
moved.y() *= scale;
// Ensure we're moving in generally the right direction...
if (speed > 0.f && moved != osg::Vec3f())
{ {
float l = moved.length(); if (duration > 0.0f)
if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 movementFromAnimation /= duration;
|| std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 else
|| std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f);
{
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 (mFloatToSurface && cls.isActor()) movementFromAnimation.x() *= scale;
{ movementFromAnimation.y() *= scale;
if (cls.getCreatureStats(mPtr).isDead()
|| (!godmode
&& cls.getCreatureStats(mPtr)
.getMagicEffects()
.getOrDefault(ESM::MagicEffect::Paralyze)
.getModifier()
> 0))
{
moved.z() = 1.0;
}
}
// Update movement if (speed > 0.f && movementFromAnimation != osg::Vec3f())
if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) {
world->queueMovement(mPtr, moved); // 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; mSkipAnim = false;
@ -2909,6 +2915,39 @@ namespace MWMechanics
MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch); 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) void CharacterController::updateHeadTracking(float duration)
{ {
const osg::Node* head = mAnimation->getNode("Bip01 Head"); const osg::Node* head = mAnimation->getNode("Bip01 Head");

View file

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

View file

@ -932,6 +932,9 @@ namespace MWMechanics
if (target.getCellRef().getLockLevel() if (target.getCellRef().getLockLevel()
< magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude < 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()) if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}");
target.getCellRef().lock(magnitude); target.getCellRef().lock(magnitude);

View file

@ -1235,9 +1235,11 @@ namespace MWRender
mRootController->setEnabled(enable); mRootController->setEnabled(enable);
if (enable) if (enable)
{ {
mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)) osg::Quat legYaw = osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1));
* osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); mRootController->setRotate(legYaw * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0)));
yawOffset = mLegsYawRadians; yawOffset = mLegsYawRadians;
// When yawing the root, also update the accumulated movement.
movement = legYaw * movement;
} }
} }
if (mSpineController) 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)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f));
stateset->setAttribute(defaultMat); stateset->setAttribute(defaultMat);
SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset);
// assign large value to effectively turn off fog // assign large value to effectively turn off fog
// shaders don't respect glDisable(GL_FOG) // shaders don't respect glDisable(GL_FOG)

View file

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

View file

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

View file

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

View file

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

View file

@ -10,9 +10,11 @@
namespace MWRender namespace MWRender
{ {
PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) PingPongCanvas::PingPongCanvas(
Shader::ShaderManager& shaderManager, const std::shared_ptr<LuminanceCalculator>& luminanceCalculator)
: mFallbackStateSet(new osg::StateSet) : mFallbackStateSet(new osg::StateSet)
, mMultiviewResolveStateSet(new osg::StateSet) , mMultiviewResolveStateSet(new osg::StateSet)
, mLuminanceCalculator(luminanceCalculator)
{ {
setUseDisplayList(false); setUseDisplayList(false);
setUseVertexBufferObjects(true); setUseVertexBufferObjects(true);
@ -26,8 +28,7 @@ namespace MWRender
addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3));
mLuminanceCalculator = LuminanceCalculator(shaderManager); mLuminanceCalculator->disable();
mLuminanceCalculator.disable();
Shader::ShaderManager::DefineMap defines; Shader::ShaderManager::DefineMap defines;
Stereo::shaderStereoDefines(defines); Stereo::shaderStereoDefines(defines);
@ -142,7 +143,7 @@ namespace MWRender
.getTexture()); .getTexture());
} }
mLuminanceCalculator.dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); mLuminanceCalculator->dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight());
if (Stereo::getStereo()) if (Stereo::getStereo())
mRenderViewport mRenderViewport
@ -158,11 +159,11 @@ namespace MWRender
{ GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT }, { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT },
{ GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_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 // 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. // 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]; 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) for (const size_t& index : filtered)
{ {
const auto& node = mPasses[index]; const auto& node = mPasses[index];
@ -202,8 +236,8 @@ namespace MWRender
node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, mTextureDepth); node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, mTextureDepth);
if (mAvgLum) if (mAvgLum)
node.mRootStateSet->setTextureAttribute( node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation,
PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId)); mLuminanceCalculator->getLuminanceTexture(frameId));
if (mTextureNormals) if (mTextureNormals)
node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals);
@ -238,6 +272,23 @@ namespace MWRender
if (pass.mRenderTarget) 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); pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
if (pass.mRenderTexture->getNumMipmapLevels() > 0) if (pass.mRenderTexture->getNumMipmapLevels() > 0)
@ -311,5 +362,7 @@ namespace MWRender
{ {
bindDestinationFbo(); bindDestinationFbo();
} }
mDirtyAttachments.clear();
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -138,6 +138,59 @@ namespace
return npcsToReplace; 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 // 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. // longer exists however. So instead of removing the item altogether, we're only removing the script.
template <class MapT> template <class MapT>
@ -538,71 +591,24 @@ namespace MWWorld
removeMissingScripts(getWritable<ESM::Script>(), getWritable<ESM::Creature>().mStatic); removeMissingScripts(getWritable<ESM::Script>(), getWritable<ESM::Creature>().mStatic);
// Validate spell effects for invalid arguments // Validate spell effects and enchantments for invalid arguments
std::vector<ESM::Spell> spellsToReplace;
auto& spells = getWritable<ESM::Spell>(); auto& spells = getWritable<ESM::Spell>();
for (ESM::Spell spell : spells) auto& enchantments = getWritable<ESM::Enchantment>();
{ auto& magicEffects = getWritable<ESM::MagicEffect>();
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);
}
std::vector<ESM::Spell> spellsToReplace = getSpellsToReplace(spells, magicEffects);
for (const ESM::Spell& spell : spellsToReplace) for (const ESM::Spell& spell : spellsToReplace)
{ {
spells.eraseStatic(spell.mId); spells.eraseStatic(spell.mId);
spells.insertStatic(spell); 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() void ESMStore::movePlayerRecord()

View file

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

View file

@ -81,9 +81,9 @@ namespace MWWorld
void fireEquipmentChangedEvent(); void fireEquipmentChangedEvent();
void storeEquipmentState( 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( 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; ContainerStoreIterator findSlot(int slot) const;

View file

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

View file

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

View file

@ -411,7 +411,7 @@ namespace
EXPECT_EQ(*result, expected); 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.mName = "Bounding Box";
mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV;
@ -427,15 +427,11 @@ namespace
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = 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); 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.mName = "Bounding Box";
mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV;
@ -453,15 +449,11 @@ namespace
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = 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); 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.mName = "Bounding Box";
mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV;
@ -483,10 +475,6 @@ namespace
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = 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); EXPECT_EQ(*result, expected);
} }
@ -519,10 +507,6 @@ namespace
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3);
expected.mCollisionBox.mCenter = 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); EXPECT_EQ(*result, expected);
} }
@ -555,10 +539,6 @@ namespace
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6); expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6);
expected.mCollisionBox.mCenter = 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); EXPECT_EQ(*result, expected);
} }
@ -977,12 +957,12 @@ namespace
} }
TEST_F(TestBulletNifLoader, 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.mData = "NCC__";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode); mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
@ -1005,13 +985,13 @@ namespace
} }
TEST_F(TestBulletNifLoader, 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); mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.mData = "NCC__";
mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode); mNiTriShape.mParents.push_back(&mNiNode);
mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
@ -1032,8 +1012,62 @@ namespace
EXPECT_EQ(*result, expected); 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, 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.mData = "NC___";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
@ -1054,35 +1088,6 @@ namespace
Resource::BulletShape expected; Resource::BulletShape expected;
expected.mCollisionShape.reset(compound.release()); 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); EXPECT_EQ(*result, expected);
} }
@ -1121,33 +1126,13 @@ namespace
EXPECT_EQ(*result, expected); 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) 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.mParents.push_back(&mNiNode);
mNiTriShape.mName = "EditorMarker"; 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) }; mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
@ -1162,18 +1147,14 @@ namespace
EXPECT_EQ(*result, expected); EXPECT_EQ(*result, expected);
} }
TEST_F(TestBulletNifLoader, TEST_F(TestBulletNifLoader, mrk_editor_marker_flag_disables_collision_for_markers)
for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes)
{ {
mNiTriShape.mParents.push_back(&mNiNode);
mNiTriShape.mName = "Tri EditorMarker";
mNiStringExtraData.mData = "MRK"; mNiStringExtraData.mData = "MRK";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.mParents.push_back(&mNiNode2); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) };
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;
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
file.mRoots.push_back(&mNiNode); file.mRoots.push_back(&mNiNode);
@ -1181,13 +1162,7 @@ namespace
const auto result = mLoader.load(file); 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; Resource::BulletShape expected;
expected.mCollisionShape.reset(compound.release());
EXPECT_EQ(*result, expected); 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; std::ostringstream oss;
oss << R"( oss << R"(
@ -165,6 +165,72 @@ osg::Group {
return oss.str(); 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 struct ShaderPrefixParams
{ {
unsigned int mShaderType; unsigned int mShaderType;
@ -194,7 +260,7 @@ osg::Group {
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node); file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager); 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)); INSTANTIATE_TEST_SUITE_P(Params, NifOsgLoaderBSShaderPrefixTest, ValuesIn(NifOsgLoaderBSShaderPrefixTest::sParams));
@ -218,11 +284,13 @@ osg::Group {
property.mTextureSet = nullptr; property.mTextureSet = nullptr;
property.mController = nullptr; property.mController = nullptr;
property.mType = GetParam().mShaderType; 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)); node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node); file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager); auto result = Loader::load(file, &mImageManager);
EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix)); EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix));
} }
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,7 +9,7 @@ namespace ESM
struct DoorState final : public ObjectState struct DoorState final : public ObjectState
{ {
int mDoorState = 0; int32_t mDoorState = 0;
void load(ESMReader& esm) override; void load(ESMReader& esm) override;
void save(ESMWriter& esm, bool inInventory = false) const 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"; } static constexpr std::string_view getRecordType() { return "Filter"; }
unsigned int mRecordFlags; uint32_t mRecordFlags;
RefId mId; RefId mId;
std::string mDescription; std::string mDescription;

View file

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

View file

@ -2,6 +2,7 @@
#define OPENMW_ESM_INVENTORYSTATE_H #define OPENMW_ESM_INVENTORYSTATE_H
#include <map> #include <map>
#include <optional>
#include "objectstate.hpp" #include "objectstate.hpp"
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
@ -19,20 +20,15 @@ namespace ESM
std::vector<ObjectState> mItems; std::vector<ObjectState> mItems;
// <Index in mItems, equipment slot> // <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; std::map<ESM::RefId, std::vector<std::pair<float, float>>> mPermanentMagicEffectMagnitudes;
TEffectMagnitudes mPermanentMagicEffectMagnitudes;
int mSelectedEnchantItem; // For inventories only std::optional<uint32_t> mSelectedEnchantItem; // For inventories only
InventoryState() virtual ~InventoryState() = default;
: mSelectedEnchantItem(-1)
{
}
virtual ~InventoryState() {}
virtual void load(ESMReader& esm); virtual void load(ESMReader& esm);
virtual void save(ESMWriter& esm) const; 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 ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum
void adjustRefNum(RefNum& refNum, const ESMReader& reader) 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 // 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) // by the present plugin (but a faulty one)
@ -124,7 +124,7 @@ namespace ESM
switch (esm.retSubName().toInt()) switch (esm.retSubName().toInt())
{ {
case fourCC("INTV"): case fourCC("INTV"):
int waterl; int32_t waterl;
esm.getHT(waterl); esm.getHT(waterl);
mWater = static_cast<float>(waterl); mWater = static_cast<float>(waterl);
mWaterInt = true; mWaterInt = true;
@ -192,7 +192,7 @@ namespace ESM
{ {
if (mWaterInt) 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); esm.writeHNT("INTV", water);
} }
else 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) if (tempCount != 0)
esm.writeHNT("NAM0", tempCount); 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)); esm.restoreContext(mContextList.at(iCtx));
} }
@ -321,7 +321,7 @@ namespace ESM
void Cell::blank() void Cell::blank()
{ {
mName = ""; mName.clear();
mRegion = ESM::RefId(); mRegion = ESM::RefId();
mWater = 0; mWater = 0;
mWaterInt = false; mWaterInt = false;

View file

@ -34,7 +34,7 @@ namespace ESM
RefNum mRefNum; RefNum mRefNum;
// Coordinates of target exterior cell // 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 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 // 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 ESMReader& esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references
void save(ESMWriter& esm, bool isDeleted = false) const; 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); } 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(); } 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 // somewhere other than the file system, you need to pre-open the
// ESMReader, and the filename must match the stored filename // ESMReader, and the filename must match the stored filename
// exactly. // exactly.
void restore(ESMReader& esm, int iCtx) const; void restore(ESMReader& esm, size_t iCtx) const;
std::string getDescription() const; std::string getDescription() const;
///< Return a short string describing the cell (mostly used for debugging/logging purpose) ///< Return a short string describing the cell (mostly used for debugging/logging purpose)

View file

@ -19,7 +19,7 @@ namespace ESM
struct ContItem struct ContItem
{ {
int mCount{ 0 }; int32_t mCount{ 0 };
ESM::RefId mItem; ESM::RefId mItem;
}; };
@ -48,12 +48,12 @@ namespace ESM
Unknown = 8 Unknown = 8
}; };
unsigned int mRecordFlags; uint32_t mRecordFlags;
RefId mId, mScript; RefId mId, mScript;
std::string mName, mModel; std::string mName, mModel;
float mWeight; // Not sure, might be max total weight allowed? float mWeight; // Not sure, might be max total weight allowed?
int mFlags; int32_t mFlags;
InventoryList mInventory; InventoryList mInventory;
void load(ESMReader& esm, bool& isDeleted); 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. /// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "Global"; } static std::string_view getRecordType() { return "Global"; }
unsigned int mRecordFlags; uint32_t mRecordFlags;
ESM::RefId mId; ESM::RefId mId;
Variant mValue; 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. /// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "GameSetting"; } static std::string_view getRecordType() { return "GameSetting"; }
unsigned int mRecordFlags; uint32_t mRecordFlags;
RefId mId; RefId mId;
Variant mValue; Variant mValue;

View file

@ -30,7 +30,7 @@ namespace ESM
break; break;
case fourCC("INDX"): case fourCC("INDX"):
{ {
int length = 0; uint32_t length = 0;
esm.getHT(length); esm.getHT(length);
mList.resize(length); mList.resize(length);
@ -87,12 +87,12 @@ namespace ESM
esm.writeHNT("DATA", mFlags); esm.writeHNT("DATA", mFlags);
esm.writeHNT("NNAM", mChanceNone); 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.writeHNCRefId(recName, item.mId);
esm.writeHNT("INTV", it->mLevel); esm.writeHNT("INTV", item.mLevel);
} }
} }

View file

@ -24,15 +24,15 @@ namespace ESM
struct LevelledListBase struct LevelledListBase
{ {
int mFlags; int32_t mFlags;
unsigned char mChanceNone; // Chance that none are selected (0-100) unsigned char mChanceNone; // Chance that none are selected (0-100)
unsigned int mRecordFlags; uint32_t mRecordFlags;
RefId mId; RefId mId;
struct LevelItem struct LevelItem
{ {
RefId mId; RefId mId;
short mLevel; uint16_t mLevel;
}; };
std::vector<LevelItem> mList; 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. // mId is merely a user friendly name for the texture in the editor.
std::string mTexture; std::string mTexture;
RefId mId; RefId mId;
int mIndex; int32_t mIndex;
void load(ESMReader& esm, bool& isDeleted); void load(ESMReader& esm, bool& isDeleted);
void save(ESMWriter& esm, bool isDeleted = false) const; void save(ESMWriter& esm, bool isDeleted = false) const;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,7 +28,7 @@ namespace ESM
static std::string_view getRecordType() { return "StartScript"; } static std::string_view getRecordType() { return "StartScript"; }
std::string mData; std::string mData;
unsigned int mRecordFlags; uint32_t mRecordFlags;
RefId mId; RefId mId;
// Load a record and add it to the list // 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. /// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "Static"; } static std::string_view getRecordType() { return "Static"; }
unsigned int mRecordFlags; uint32_t mRecordFlags;
RefId mId; RefId mId;
std::string mModel; std::string mModel;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -76,6 +76,9 @@ void ESM4::LandTexture::load(ESM4::Reader& reader)
case ESM4::SUB_MNAM: case ESM4::SUB_MNAM:
reader.getFormId(mMaterial); reader.getFormId(mMaterial);
break; // TES5, FO4 break; // TES5, FO4
case ESM4::SUB_INAM:
reader.get(mMaterialFlags);
break; // SSE
default: default:
throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); 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 load(ESM4::Reader& reader);
// void save(ESM4::Writer& writer) const; // void save(ESM4::Writer& writer) const;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -29,6 +29,16 @@ namespace fx
std::optional<int> mWidth; std::optional<int> mWidth;
std::optional<int> mHeight; 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 std::tuple<int, int> get(int width, int height) const
{ {
int scaledWidth = width; int scaledWidth = width;
@ -53,6 +63,17 @@ namespace fx
osg::ref_ptr<osg::Texture2D> mTarget = new osg::Texture2D; osg::ref_ptr<osg::Texture2D> mTarget = new osg::Texture2D;
SizeProxy mSize; SizeProxy mSize;
bool mMipMap = false; 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> template <class T>

View file

@ -8,9 +8,7 @@ namespace LuaUi
{ {
void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize)
{ {
mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); mCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight());
mAlign = MyGUI::Align::Stretch;
MyGUI::TileRect::_setAlign(_oldsize);
mTileSize = mSetTileSize; mTileSize = mSetTileSize;
// zero tilesize stands for not tiling // zero tilesize stands for not tiling
@ -25,6 +23,8 @@ namespace LuaUi
mTileSize.width = 1e7; mTileSize.width = 1e7;
if (mTileSize.height <= 0) if (mTileSize.height <= 0)
mTileSize.height = 1e7; mTileSize.height = 1e7;
MyGUI::TileRect::_updateView();
} }
void LuaImage::initialize() void LuaImage::initialize()
@ -55,13 +55,13 @@ namespace LuaUi
if (texture != nullptr) if (texture != nullptr)
textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); 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) if (atlasCoord.width == 0)
atlasCoord.width = textureSize.width; atlasCoord.width = textureSize.width;
if (atlasCoord.height == 0) if (atlasCoord.height == 0)
atlasCoord.height = textureSize.height; atlasCoord.height = textureSize.height;
mTileRect->updateSize(MyGUI::IntSize(tileH ? atlasCoord.width : 0, tileV ? atlasCoord.height : 0));
setImageTile(atlasCoord.size());
setImageCoord(atlasCoord); setImageCoord(atlasCoord);
setColour(propertyValue("color", MyGUI::Colour(1, 1, 1, 1))); 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 = createWidget<MyGUI::EditBox>("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default);
mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange);
mEditBox->setMaxTextLength(std::numeric_limits<std::size_t>::max());
registerEvents(mEditBox); registerEvents(mEditBox);
WidgetExtension::initialize(); WidgetExtension::initialize();
} }

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