From 4b0aeb406601cb30e63161b3bcceb6ecfbe8e85c Mon Sep 17 00:00:00 2001 From: dteviot Date: Sun, 15 Mar 2015 14:07:47 +1300 Subject: [PATCH 1/7] consolidate random number logic Note, I suspect Rng::rollClosedProbability() is not needed. The only difference between it and rollProbability() is that one time in 37k (on Windows), it will give an output of 1.0. On some versions of Linux, the value of 1.0 will occur about 1 time in 4 billion. (cherry picked from commit 3f28634d1f617691c672e41a3ee950e6daec8c77) # Conflicts: # apps/openmw/mwclass/creature.cpp # apps/openmw/mwclass/npc.cpp # apps/openmw/mwgui/pickpocketitemmodel.cpp # apps/openmw/mwgui/waitdialog.cpp # apps/openmw/mwmechanics/combat.cpp # apps/openmw/mwmechanics/mechanicsmanagerimp.cpp # components/CMakeLists.txt # libs/openengine/misc/rng.cpp --- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 2 +- apps/openmw/mwgui/waitdialog.cpp | 3 +-- apps/openmw/mwmechanics/combat.cpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- components/CMakeLists.txt | 7 +------ libs/openengine/misc/rng.cpp | 2 +- 8 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 13fd22a0ce..4043e9883e 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -250,7 +250,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); - if(OEngine::Misc::Rng::roll0to99() >= hitchance) + if(OEngine::Misc::Rng::rollProbability() >= hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index bc806078af..c71c54f993 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -382,7 +382,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); - if (OEngine::Misc::Rng::roll0to99() >= hitchance) + if (OEngine::Misc::Rng::rollProbability() >= hitchance / 100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index b8ac20f99d..bc7c5528e8 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -20,7 +20,7 @@ namespace MWGui { for (size_t i = 0; igetItemCount(); ++i) { - if (OEngine::Misc::Rng::roll0to99() > chance) + if (chance <= OEngine::Misc::Rng::roll0to99()) mHiddenItems.push_back(mSourceModel->getItem(i)); } } diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index ed261e7eb0..163222c16e 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -155,9 +155,8 @@ namespace MWGui if (!region->mSleepList.empty()) { // figure out if player will be woken while sleeping - int x = OEngine::Misc::Rng::rollDice(hoursToWait); float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->getFloat(); - if (x < fSleepRandMod * hoursToWait) + if (OEngine::Misc::Rng::rollProbability() > fSleepRandMod) { float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->getFloat(); mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 045f0108ca..c5fc34507b 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -187,7 +187,7 @@ namespace MWMechanics int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); - if (OEngine::Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) + if (OEngine::Misc::Rng::rollProbability() >= getHitChance(attacker, victim, skillValue) / 100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index dc388555ed..ee158a15dd 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -741,7 +741,7 @@ namespace MWMechanics float x = 0; float y = 0; - int roll = OEngine::Misc::Rng::roll0to99(); + float roll = OEngine::Misc::Rng::rollClosedProbability() * 100; if (type == PT_Admire) { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b2dbd3ff6c..a5934b5f1d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -166,14 +166,9 @@ include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components - ${Boost_SYSTEM_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} - ${Boost_THREAD_LIBRARY} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_WAVE_LIBRARY} + ${Boost_LIBRARIES} ${OGRE_LIBRARIES} ${OENGINE_LIBRARY} - ${BULLET_LIBRARIES} ) if (WIN32) diff --git a/libs/openengine/misc/rng.cpp b/libs/openengine/misc/rng.cpp index 3d50400df0..140aa337eb 100644 --- a/libs/openengine/misc/rng.cpp +++ b/libs/openengine/misc/rng.cpp @@ -26,4 +26,4 @@ namespace Misc { } } -} +} \ No newline at end of file From b4cdb965dc0c7a293b3df9ccc9bbe25af28c4f90 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 22 Apr 2015 17:58:55 +0200 Subject: [PATCH 2/7] Move rng to components (cherry picked from commit 8c810e36203b93780cae198a3f7e2fb33ee54b5d) # Conflicts: # apps/openmw/engine.cpp # apps/openmw/mwclass/npc.cpp # components/misc/rng.cpp # components/sceneutil/lightcontroller.cpp # libs/openengine/CMakeLists.txt --- apps/openmw/engine.cpp | 4 +- apps/openmw/mwclass/creature.cpp | 8 +- apps/openmw/mwclass/npc.cpp | 12 +- apps/openmw/mwgui/jailscreen.cpp | 4 +- apps/openmw/mwgui/loadingscreen.cpp | 4 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 4 +- apps/openmw/mwgui/recharge.cpp | 4 +- apps/openmw/mwgui/tradewindow.cpp | 4 +- apps/openmw/mwgui/waitdialog.cpp | 4 +- apps/openmw/mwmechanics/activespells.cpp | 4 +- apps/openmw/mwmechanics/aicombat.cpp | 24 ++-- apps/openmw/mwmechanics/aiwander.cpp | 10 +- apps/openmw/mwmechanics/alchemy.cpp | 6 +- apps/openmw/mwmechanics/combat.cpp | 10 +- apps/openmw/mwmechanics/disease.hpp | 4 +- apps/openmw/mwmechanics/enchanting.cpp | 4 +- apps/openmw/mwmechanics/levelledlist.hpp | 6 +- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +- apps/openmw/mwmechanics/pickpocket.cpp | 4 +- apps/openmw/mwmechanics/repair.cpp | 4 +- apps/openmw/mwmechanics/security.cpp | 6 +- apps/openmw/mwmechanics/spellcasting.cpp | 16 +-- apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwrender/sky.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 8 +- apps/openmw/mwworld/inventorystore.cpp | 2 +- apps/openmw/mwworld/store.hpp | 4 +- apps/openmw/mwworld/weather.cpp | 8 +- apps/openmw/mwworld/worldimp.cpp | 6 +- components/CMakeLists.txt | 2 +- components/interpreter/miscopcodes.hpp | 4 +- {libs/openengine => components}/misc/rng.cpp | 5 +- {libs/openengine => components}/misc/rng.hpp | 6 +- components/sceneutil/lightcontroller.cpp | 129 ++++++++++++++++++ libs/openengine/CMakeLists.txt | 7 +- 36 files changed, 230 insertions(+), 111 deletions(-) rename {libs/openengine => components}/misc/rng.cpp (93%) rename {libs/openengine => components}/misc/rng.hpp (88%) create mode 100644 components/sceneutil/lightcontroller.cpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9d40ebb1b0..539539db94 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -11,7 +11,7 @@ #include -#include +#include #include @@ -194,7 +194,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mNewGame (false) , mCfgMgr(configurationManager) { - OEngine::Misc::Rng::init(); + Misc::Rng::init(); std::srand ( static_cast(std::time(NULL)) ); MWClass::registerClasses(); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4043e9883e..87c4094ca4 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,6 +1,6 @@ #include "creature.hpp" -#include +#include #include #include @@ -250,7 +250,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); - if(OEngine::Misc::Rng::rollProbability() >= hitchance/100.0f) + if(Misc::Rng::rollProbability() >= hitchance/100.0f) { victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -376,7 +376,7 @@ namespace MWClass float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().iKnockDownOddsMult->getInt() * 0.01f + getGmst().iKnockDownOddsBase->getInt(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= OEngine::Misc::Rng::roll0to99()) + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) { getCreatureStats(ptr).setKnockedDown(true); @@ -680,7 +680,7 @@ namespace MWClass ++sound; } if(!sounds.empty()) - return sounds[OEngine::Misc::Rng::rollDice(sounds.size())]->mSound; + return sounds[Misc::Rng::rollDice(sounds.size())]->mSound; } if (type == ESM::SoundGenerator::Land) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c71c54f993..221dd3d8b6 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -2,9 +2,7 @@ #include -#include - -#include +#include #include #include @@ -382,7 +380,7 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); - if (OEngine::Misc::Rng::rollProbability() >= hitchance / 100.0f) + if (Misc::Rng::rollProbability() >= hitchance / 100.0f) { othercls.onHit(victim, 0.0f, false, weapon, ptr, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -512,7 +510,7 @@ namespace MWClass const GMST& gmst = getGmst(); int chance = store.get().find("iVoiceHitOdds")->getInt(); - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); } @@ -521,7 +519,7 @@ namespace MWClass float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt(); - if (ishealth && agilityTerm <= damage && knockdownTerm <= OEngine::Misc::Rng::roll0to99()) + if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) { getCreatureStats(ptr).setKnockedDown(true); @@ -547,7 +545,7 @@ namespace MWClass MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_RightPauldron, MWWorld::InventoryStore::Slot_LeftGauntlet, MWWorld::InventoryStore::Slot_RightGauntlet }; - int hitslot = hitslots[OEngine::Misc::Rng::rollDice(20)]; + int hitslot = hitslots[Misc::Rng::rollDice(20)]; float unmitigatedDamage = damage; float x = damage / (damage + getArmorRating(ptr)); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 936da2d8e2..e4a3a31478 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -85,7 +85,7 @@ namespace MWGui std::set skills; for (int day=0; day #include -#include +#include #include @@ -148,7 +148,7 @@ namespace MWGui if (!mResources.empty()) { - std::string const & randomSplash = mResources.at(OEngine::Misc::Rng::rollDice(mResources.size())); + std::string const & randomSplash = mResources.at(Misc::Rng::rollDice(mResources.size())); Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index bc7c5528e8..2620e56609 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -1,6 +1,6 @@ #include "pickpocketitemmodel.hpp" -#include +#include #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" @@ -20,7 +20,7 @@ namespace MWGui { for (size_t i = 0; igetItemCount(); ++i) { - if (chance <= OEngine::Misc::Rng::roll0to99()) + if (chance <= Misc::Rng::roll0to99()) mHiddenItems.push_back(mSourceModel->getItem(i)); } } diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index b7280565b7..76961af5da 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include @@ -165,7 +165,7 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) intelligenceTerm = 1; float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll < x) { std::string soul = gem.getCellRef().getSoul(); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index bdcf28bf20..d2ffe60b1f 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include @@ -368,7 +368,7 @@ namespace MWGui else x += abs(int(npcTerm - pcTerm)); - int roll = OEngine::Misc::Rng::rollDice(100) + 1; + int roll = Misc::Rng::rollDice(100) + 1; if(roll > x || (mCurrentMerchantOffer < 0) != (mCurrentBalance < 0)) //trade refused { MWBase::Environment::get().getWindowManager()-> diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 163222c16e..cc4de51b69 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include @@ -156,7 +156,7 @@ namespace MWGui { // figure out if player will be woken while sleeping float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->getFloat(); - if (OEngine::Misc::Rng::rollProbability() > fSleepRandMod) + if (Misc::Rng::rollProbability() > fSleepRandMod) { float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->getFloat(); mInterruptAt = hoursToWait - int(fSleepRestMod * hoursToWait); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index a6cc9af8ef..7068310a0d 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,6 +1,6 @@ #include "activespells.hpp" -#include +#include #include @@ -231,7 +231,7 @@ namespace MWMechanics { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) { - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) mSpells.erase(it++); else ++it; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5f0eff4e97..94d648ea3c 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include @@ -395,7 +395,7 @@ namespace MWMechanics if (!distantCombat) attackType = chooseBestAttack(weapon, movement); else attackType = ESM::Weapon::AT_Chop; // cause it's =0 - strength = OEngine::Misc::Rng::rollClosedProbability(); + strength = Misc::Rng::rollClosedProbability(); // Note: may be 0 for some animations timerAttack = minMaxAttackDuration[attackType][0] + @@ -406,7 +406,7 @@ namespace MWMechanics { const MWWorld::ESMStore &store = world->getStore(); int chance = store.get().find("iVoiceAttackOdds")->getInt(); - if (OEngine::Misc::Rng::roll0to99() < chance) + if (Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); } @@ -513,17 +513,17 @@ namespace MWMechanics { if(movement.mPosition[0] || movement.mPosition[1]) { - timerCombatMove = 0.1f + 0.1f * OEngine::Misc::Rng::rollClosedProbability(); + timerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); combatMove = true; } // only NPCs are smart enough to use dodge movements else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) { //apply sideway movement (kind of dodging) with some probability - if (OEngine::Misc::Rng::rollClosedProbability() < 0.25) + if (Misc::Rng::rollClosedProbability() < 0.25) { - movement.mPosition[0] = OEngine::Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; - timerCombatMove = 0.05f + 0.15f * OEngine::Misc::Rng::rollClosedProbability(); + movement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; + timerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); combatMove = true; } } @@ -638,7 +638,7 @@ namespace MWMechanics float s2 = speed2 * t; float t_swing = minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + - (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * OEngine::Misc::Rng::rollClosedProbability(); + (minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * Misc::Rng::rollClosedProbability(); if (t + s2/speed1 <= t_swing) { @@ -750,10 +750,10 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: if (weapon == NULL) { //hand-to-hand deal equal damage for each type - float roll = OEngine::Misc::Rng::rollClosedProbability(); + float roll = Misc::Rng::rollClosedProbability(); if(roll <= 0.333f) //side punch { - movement.mPosition[0] = OEngine::Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f; + movement.mPosition[0] = Misc::Rng::rollClosedProbability() ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } @@ -777,10 +777,10 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: float total = static_cast(slash + chop + thrust); - float roll = OEngine::Misc::Rng::rollClosedProbability(); + float roll = Misc::Rng::rollClosedProbability(); if(roll <= (slash/total)) { - movement.mPosition[0] = (OEngine::Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; + movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; movement.mPosition[1] = 0; attackType = ESM::Weapon::AT_Slash; } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d89a29e1d0..dffffb1d84 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include @@ -329,7 +329,7 @@ namespace MWMechanics static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() .get().find("fVoiceIdleOdds")->getFloat(); - float roll = OEngine::Misc::Rng::rollProbability() * 10000.0f; + float roll = Misc::Rng::rollProbability() * 10000.0f; // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds // due to the roll being an integer. @@ -504,7 +504,7 @@ namespace MWMechanics if(!storage.mPathFinder.isPathConstructed()) { assert(mAllowedNodes.size()); - unsigned int randNode = OEngine::Misc::Rng::rollDice(mAllowedNodes.size()); + unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); // NOTE: initially constructed with local (i.e. cell) co-ordinates Ogre::Vector3 destNodePos(PathFinder::MakeOgreVector3(mAllowedNodes[randNode])); @@ -631,7 +631,7 @@ namespace MWMechanics .get().find("fIdleChanceMultiplier")->getFloat(); unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); - unsigned short randSelect = (int)(OEngine::Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); + unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { playedIdle = counter+2; @@ -653,7 +653,7 @@ namespace MWMechanics state.moveIn(new AiWanderStorage()); - int index = OEngine::Misc::Rng::rollDice(mAllowedNodes.size()); + int index = Misc::Rng::rollDice(mAllowedNodes.size()); ESM::Pathgrid::Point dest = mAllowedNodes[index]; // apply a slight offset to prevent overcrowding diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 42dd3048fa..2c5e585890 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -298,7 +298,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = OEngine::Misc::Rng::rollDice(6); + int index = Misc::Rng::rollDice(6); assert (index>=0 && index<6); static const char *meshes[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; @@ -474,7 +474,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na return Result_RandomFailure; } - if (getAlchemyFactor() < OEngine::Misc::Rng::roll0to99()) + if (getAlchemyFactor() < Misc::Rng::roll0to99()) { removeIngredients(); return Result_RandomFailure; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index c5fc34507b..42aed3da37 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -109,7 +109,7 @@ namespace MWMechanics int iBlockMinChance = gmst.find("iBlockMinChance")->getInt(); x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); - if (OEngine::Misc::Rng::roll0to99() < x) + if (Misc::Rng::roll0to99() < x) { // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); @@ -187,7 +187,7 @@ namespace MWMechanics int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); - if (OEngine::Misc::Rng::rollProbability() >= getHitChance(attacker, victim, skillValue) / 100.0f) + if (Misc::Rng::rollProbability() >= getHitChance(attacker, victim, skillValue) / 100.0f) { victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); @@ -225,7 +225,7 @@ namespace MWMechanics && !appliedEnchantment) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); - if (OEngine::Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) + if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } @@ -292,7 +292,7 @@ namespace MWMechanics saveTerm *= 1.25f * normalisedFatigue; - float x = std::max(0.f, saveTerm - OEngine::Misc::Rng::roll0to99()); + float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99()); int element = ESM::MagicEffect::FireDamage; if (i == 1) diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 0153be3dc8..cf21f3806b 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MECHANICS_DISEASE_H #define OPENMW_MECHANICS_DISEASE_H -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" @@ -51,7 +51,7 @@ namespace MWMechanics continue; int x = static_cast(fDiseaseXferChance * 100 * resist); - if (OEngine::Misc::Rng::rollDice(10000) < x) + if (Misc::Rng::rollDice(10000) < x) { // Contracted disease! actor.getClass().getCreatureStats(actor).getSpells().add(it->first); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index bb02fb41d3..2cb963e282 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -1,6 +1,6 @@ #include "enchanting.hpp" -#include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -70,7 +70,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(getEnchantChance() <= (OEngine::Misc::Rng::roll0to99())) + if(getEnchantChance() <= (Misc::Rng::roll0to99())) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index 20b87a3a96..14b68b9cd1 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MECHANICS_LEVELLEDLIST_H #define OPENMW_MECHANICS_LEVELLEDLIST_H -#include +#include #include "../mwworld/ptr.hpp" #include "../mwworld/esmstore.hpp" @@ -24,7 +24,7 @@ namespace MWMechanics failChance += levItem->mChanceNone; - if (OEngine::Misc::Rng::roll0to99() < failChance) + if (Misc::Rng::roll0to99() < failChance) return std::string(); std::vector candidates; @@ -53,7 +53,7 @@ namespace MWMechanics } if (candidates.empty()) return std::string(); - std::string item = candidates[OEngine::Misc::Rng::rollDice(candidates.size())]; + std::string item = candidates[Misc::Rng::rollDice(candidates.size())]; // Vanilla doesn't fail on nonexistent items in levelled lists if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index ee158a15dd..2bf23b586c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -2,7 +2,7 @@ #include "mechanicsmanagerimp.hpp" #include "npcstats.hpp" -#include +#include #include @@ -741,7 +741,7 @@ namespace MWMechanics float x = 0; float y = 0; - float roll = OEngine::Misc::Rng::rollClosedProbability() * 100; + float roll = Misc::Rng::rollClosedProbability() * 100; if (type == PT_Admire) { @@ -1399,7 +1399,7 @@ namespace MWMechanics float target = x - y; - return (OEngine::Misc::Rng::roll0to99() >= target); + return (Misc::Rng::roll0to99() >= target); } void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp index 561011df38..eca24606e6 100644 --- a/apps/openmw/mwmechanics/pickpocket.cpp +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -1,6 +1,6 @@ #include "pickpocket.hpp" -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -41,7 +41,7 @@ namespace MWMechanics int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() .find("iPickMaxChance")->getInt(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (t < pcSneak / iPickMinChance) { return (roll > int(pcSneak / iPickMinChance)); diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index b5058fb88e..fa429bbeef 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -48,7 +48,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) float x = (0.1f * pcStrength + 0.1f * pcLuck + armorerSkill) * fatigueTerm; - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll <= x) { int y = static_cast(fRepairAmountMult * toolQuality * roll); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 3f72f1b669..9eab5bfef0 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -1,6 +1,6 @@ #include "security.hpp" -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -50,7 +50,7 @@ namespace MWMechanics else { MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, lock); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { lock.getClass().unlock(lock); resultMessage = "#{sLockSuccess}"; @@ -91,7 +91,7 @@ namespace MWMechanics else { MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, trap); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { trap.getCellRef().setTrap(""); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 1f3a888279..ce0f7c12e1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -282,7 +282,7 @@ namespace MWMechanics if (castChance > 0) x *= 50 / castChance; - float roll = OEngine::Misc::Rng::rollClosedProbability() * 100; + float roll = Misc::Rng::rollClosedProbability() * 100; if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) roll -= resistance; @@ -385,7 +385,7 @@ namespace MWMechanics target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).getMagnitude() : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).getMagnitude(); - if (OEngine::Misc::Rng::roll0to99() <= x) + if (Misc::Rng::roll0to99() <= x) { // Fully resisted, show message if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) @@ -415,7 +415,7 @@ namespace MWMechanics if (spell && caster != target && target.getClass().isActor()) { float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude(); - absorbed = (OEngine::Misc::Rng::roll0to99() < absorb); + absorbed = (Misc::Rng::roll0to99() < absorb); if (absorbed) { const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); @@ -463,7 +463,7 @@ namespace MWMechanics if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - bool isReflected = (OEngine::Misc::Rng::roll0to99() < reflect); + bool isReflected = (Misc::Rng::roll0to99() < reflect); if (isReflected) { const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); @@ -491,7 +491,7 @@ namespace MWMechanics if (magnitudeMult > 0 && !absorbed) { - float random = OEngine::Misc::Rng::rollClosedProbability(); + float random = Misc::Rng::rollClosedProbability(); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; magnitude *= magnitudeMult; @@ -823,7 +823,7 @@ namespace MWMechanics // Check success float successChance = getSpellSuccessChance(spell, mCaster); - if (OEngine::Misc::Rng::roll0to99() >= successChance) + if (Misc::Rng::roll0to99() >= successChance) { if (mCaster == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); @@ -901,7 +901,7 @@ namespace MWMechanics + 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) * creatureStats.getFatigueTerm(); - int roll = OEngine::Misc::Rng::roll0to99(); + int roll = Misc::Rng::roll0to99(); if (roll > x) { // "X has no effect on you" diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 4636ecfae1..ef75d56714 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -37,7 +37,7 @@ namespace MWMechanics for (unsigned int i=0; imEffects.mList.size();++i) { if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) - random[i] = OEngine::Misc::Rng::rollClosedProbability(); + random[i] = Misc::Rng::rollClosedProbability(); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f4943ba554..2d0e054112 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -12,7 +12,7 @@ #include -#include +#include #include @@ -103,7 +103,7 @@ void HeadAnimationTime::setEnabled(bool enabled) void HeadAnimationTime::resetBlinkTimer() { - mBlinkTimer = -(2.0f + OEngine::Misc::Rng::rollDice(6)); + mBlinkTimer = -(2.0f + Misc::Rng::rollDice(6)); } void HeadAnimationTime::update(float dt) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index fd439050c5..2aab1c0cc2 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -19,7 +19,7 @@ #include -#include +#include #include #include diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index c7fb9ea506..731742361c 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -226,7 +226,7 @@ namespace MWSound if(!filelist.size()) return; - int i = OEngine::Misc::Rng::rollDice(filelist.size()); + int i = Misc::Rng::rollDice(filelist.size()); // Don't play the same music track twice in a row if (filelist[i] == mLastPlayedMusic) @@ -561,7 +561,7 @@ namespace MWSound if(!cell->isExterior() || sTimePassed < sTimeToNextEnvSound) return; - float a = OEngine::Misc::Rng::rollClosedProbability(); + float a = Misc::Rng::rollClosedProbability(); // NOTE: We should use the "Minimum Time Between Environmental Sounds" and // "Maximum Time Between Environmental Sounds" fallback settings here. sTimeToNextEnvSound = 5.0f*a + 15.0f*(1.0f-a); @@ -590,7 +590,7 @@ namespace MWSound return; } - int r = OEngine::Misc::Rng::rollDice(total); + int r = Misc::Rng::rollDice(total); int pos = 0; soundIter = regn->mSoundList.begin(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 5b0c2311a8..e85dbbe58e 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -366,7 +366,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // Roll some dice, one for each effect params.resize(enchantment.mEffects.mList.size()); for (unsigned int i=0; i #include -#include +#include #include #include @@ -186,7 +186,7 @@ namespace MWWorld std::vector results; std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); if(!results.empty()) - return results[OEngine::Misc::Rng::rollDice(results.size())]; + return results[Misc::Rng::rollDice(results.size())]; return NULL; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 41ed7c20da..6e2620188f 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -3,7 +3,7 @@ #include "weather.hpp" -#include +#include #include @@ -527,7 +527,7 @@ void WeatherManager::update(float duration, bool paused) if (mThunderSoundDelay <= 0) { // pick a random sound - int sound = OEngine::Misc::Rng::rollDice(4); + int sound = Misc::Rng::rollDice(4); std::string* soundName = NULL; if (sound == 0) soundName = &mThunderSoundID0; else if (sound == 1) soundName = &mThunderSoundID1; @@ -543,7 +543,7 @@ void WeatherManager::update(float duration, bool paused) mRendering->getSkyManager()->setLightningStrength( mThunderFlash / mThunderThreshold ); else { - mThunderChanceNeeded = static_cast(OEngine::Misc::Rng::rollDice(100)); + mThunderChanceNeeded = static_cast(Misc::Rng::rollDice(100)); mThunderChance = 0; mRendering->getSkyManager()->setLightningStrength( 0.f ); } @@ -625,7 +625,7 @@ std::string WeatherManager::nextWeather(const ESM::Region* region) const * 70% will be greater than 30 (in theory). */ - int chance = OEngine::Misc::Rng::rollDice(100) + 1; // 1..100 + int chance = Misc::Rng::rollDice(100) + 1; // 1..100 int sum = 0; unsigned int i = 0; for (; i < probability.size(); ++i) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b6d5c8e932..71cf4a1433 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include @@ -3145,7 +3145,7 @@ namespace MWWorld const ESM::CreatureLevList* list = getStore().get().find(creatureList); int iNumberCreatures = getStore().get().find("iNumberCreatures")->getInt(); - int numCreatures = 1 + OEngine::Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] + int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] for (int i=0; igetFallbackString(modelName.str()); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a5934b5f1d..da0428ec25 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -70,7 +70,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - utf8stream stringops resourcehelpers + utf8stream stringops resourcehelpers rng ) IF(NOT WIN32 AND NOT APPLE) diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index f566a54997..c49bbeb013 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -12,7 +12,7 @@ #include "runtime.hpp" #include "defines.hpp" -#include +#include namespace Interpreter { @@ -148,7 +148,7 @@ namespace Interpreter throw std::runtime_error ( "random: argument out of range (Don't be so negative!)"); - Type_Integer value = OEngine::Misc::Rng::rollDice(limit); // [o, limit) + Type_Integer value = Misc::Rng::rollDice(limit); // [o, limit) runtime[0].mInteger = value; } diff --git a/libs/openengine/misc/rng.cpp b/components/misc/rng.cpp similarity index 93% rename from libs/openengine/misc/rng.cpp rename to components/misc/rng.cpp index 140aa337eb..df0fc687e8 100644 --- a/libs/openengine/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -2,8 +2,8 @@ #include #include -namespace OEngine { -namespace Misc { +namespace Misc +{ void Rng::init() { @@ -26,4 +26,3 @@ namespace Misc { } } -} \ No newline at end of file diff --git a/libs/openengine/misc/rng.hpp b/components/misc/rng.hpp similarity index 88% rename from libs/openengine/misc/rng.hpp rename to components/misc/rng.hpp index 4e1da17e10..01fcdd763d 100644 --- a/libs/openengine/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -1,9 +1,8 @@ -#ifndef MISC_RNG_H -#define MISC_RNG_H +#ifndef OPENMW_COMPONENTS_MISC_RNG_H +#define OPENMW_COMPONENTS_MISC_RNG_H #include -namespace OEngine { namespace Misc { @@ -30,7 +29,6 @@ public: static int roll0to99() { return rollDice(100); } }; -} } #endif diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp new file mode 100644 index 0000000000..484cea4476 --- /dev/null +++ b/components/sceneutil/lightcontroller.cpp @@ -0,0 +1,129 @@ +#include "lightcontroller.hpp" + +#include + +#include + +#include + +#include + +namespace +{ + + float pulseAmplitude(float time) + { + return std::sin(time); + } + + float flickerAmplitude(float time) + { + static const float fb = 1.17024f; + static const float f[3] = { 1.5708f, 4.18774f, 5.19934f }; + static const float o[3] = { 0.804248f, 2.11115f, 3.46832f }; + static const float m[3] = { 1.0f, 0.785f, 0.876f }; + static const float s = 0.394f; + + float v = 0.0f; + for(int i = 0;i < 3;++i) + v += std::sin(fb*time*f[i] + o[1])*m[i]; + return v * s; + } + + float flickerFrequency(float phase) + { + static const float fa = 0.785398f; + static const float tdo = 0.94f; + static const float tdm = 2.48f; + + return tdo + tdm*std::sin(fa * phase); + } + +} + +namespace SceneUtil +{ + + LightController::LightController() + : mType(LT_Normal) + , mPhase((Misc::Rng::rollClosedProbability() * 2.f - 1.f) * 500.f) + , mLastTime(0.0) + , mDeltaCount(0.f) + , mDirection(1.f) + { + } + + void LightController::setType(LightController::LightType type) + { + mType = type; + } + + void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv) + { + double time = nv->getFrameStamp()->getSimulationTime(); + if (time == mLastTime) + return; + + float dt = static_cast(time - mLastTime); + mLastTime = time; + + float brightness = 1.0f; + float cycle_time; + float time_distortion; + + const float pi = 3.14159265359; + + if(mType == LT_Pulse || mType == LT_PulseSlow) + { + cycle_time = 2.0f * pi; + time_distortion = 20.0f; + } + else + { + static const float fa = 0.785398f; + static const float phase_wavelength = 120.0f * pi / fa; + + cycle_time = 500.0f; + mPhase = std::fmod(mPhase + dt, phase_wavelength); + time_distortion = flickerFrequency(mPhase); + } + + mDeltaCount += mDirection*dt*time_distortion; + if(mDirection > 0 && mDeltaCount > +cycle_time) + { + mDirection = -1.0f; + mDeltaCount = 2.0f*cycle_time - mDeltaCount; + } + if(mDirection < 0 && mDeltaCount < -cycle_time) + { + mDirection = +1.0f; + mDeltaCount = -2.0f*cycle_time - mDeltaCount; + } + + static const float fast = 4.0f/1.0f; + static const float slow = 1.0f/1.0f; + + // These formulas are just guesswork, but they work pretty well + if(mType == LT_Normal) + { + // Less than 1/255 light modifier for a constant light: + brightness = 1.0f + flickerAmplitude(mDeltaCount*slow)/255.0f; + } + else if(mType == LT_Flicker) + brightness = 0.75f + flickerAmplitude(mDeltaCount*fast)*0.25f; + else if(mType == LT_FlickerSlow) + brightness = 0.75f + flickerAmplitude(mDeltaCount*slow)*0.25f; + else if(mType == LT_Pulse) + brightness = 1.0f + pulseAmplitude(mDeltaCount*fast)*0.25f; + else if(mType == LT_PulseSlow) + brightness = 1.0f + pulseAmplitude(mDeltaCount*slow)*0.25f; + + static_cast(node)->getLight()->setDiffuse(mDiffuseColor * brightness); + } + + void LightController::setDiffuse(osg::Vec4f color) + { + mDiffuseColor = color; + } + +} diff --git a/libs/openengine/CMakeLists.txt b/libs/openengine/CMakeLists.txt index 3542becf64..e9a1bcd9e2 100644 --- a/libs/openengine/CMakeLists.txt +++ b/libs/openengine/CMakeLists.txt @@ -23,12 +23,7 @@ set(OENGINE_BULLET bullet/trace.h ) -set(OENGINE_MISC - misc/rng.cpp - misc/rng.hpp -) - -set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET} ${OENGINE_MISC}) +set(OENGINE_ALL ${OENGINE_GUI} ${OENGINE_BULLET}) set(OENGINE_LIBRARY "oengine") set(OENGINE_LIBRARY ${OENGINE_LIBRARY} PARENT_SCOPE) From 98ea184ddae63b4a55ff2b9893af9ede371f3023 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 27 Nov 2015 21:40:36 +0100 Subject: [PATCH 3/7] Do not assert() for invalid land data in plugins (Bug #3037) The resizing of LTEX store to the correct number of plugins was done in the load() method, but the load method won't be called if a plugin contains LAND records but doesn't contain LTEX records. For such plugins the Store::search() function would then fail an assertion. (cherry picked from commit 4687c4baad4397d55d42ab37b225dbbf25cf8781) # Conflicts: # apps/openmw/mwworld/store.cpp # apps/openmw/mwworld/store.hpp --- apps/openmw/mwworld/esmstore.cpp | 6 + apps/openmw/mwworld/store.cpp | 239 +++++++++++++++++++++++++++++++ apps/openmw/mwworld/store.hpp | 8 +- 3 files changed, 250 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 2a302e0825..91233cf70b 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -31,6 +31,12 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) ESM::Dialogue *dialogue = 0; + // Land texture loading needs to use a separate internal store for each plugin. + // We set the number of plugins here to avoid continual resizes during loading, + // and so we can properly verify if valid plugin indices are being passed to the + // LandTexture Store retrieval methods. + mLandTextures.resize(esm.getGlobalReaderList()->size()); + /// \todo Move this to somewhere else. ESMReader? // Cache parent esX files by tracking their indices in the global list of // all files/readers used by the engine. This will greaty accelerate diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 767a33b135..925e9c6053 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -96,6 +96,245 @@ void Store::load(ESM::ESMReader &esm) wipecell->mLeasedRefs.erase(it_lease); *itold = *it; } + ++sharedIter; + } + mStatic.erase(it); + } + + return true; + } + + template + bool Store::erase(const std::string &id) + { + std::string key = Misc::StringUtils::lowerCase(id); + typename Dynamic::iterator it = mDynamic.find(key); + if (it == mDynamic.end()) { + return false; + } + mDynamic.erase(it); + + // have to reinit the whole shared part + assert(mShared.size() >= mStatic.size()); + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { + mShared.push_back(&it->second); + } + return true; + } + template + bool Store::erase(const T &item) + { + return erase(item.mId); + } + template + void Store::write (ESM::ESMWriter& writer, Loading::Listener& progress) const + { + for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); + ++iter) + { + writer.startRecord (T::sRecordId); + iter->second.save (writer); + writer.endRecord (T::sRecordId); + } + } + template + RecordId Store::read(ESM::ESMReader& reader) + { + T record; + bool isDeleted = false; + + record.load (reader, isDeleted); + insert (record); + + return RecordId(record.mId, isDeleted); + } + + // LandTexture + //========================================================================= + Store::Store() + { + mStatic.push_back(LandTextureList()); + LandTextureList <exl = mStatic[0]; + // More than enough to hold Morrowind.esm. Extra lists for plugins will we + // added on-the-fly in a different method. + ltexl.reserve(128); + } + const ESM::LandTexture *Store::search(size_t index, size_t plugin) const + { + assert(plugin < mStatic.size()); + const LandTextureList <exl = mStatic[plugin]; + + if (index >= ltexl.size()) + return NULL; + return <exl[index]; + } + const ESM::LandTexture *Store::find(size_t index, size_t plugin) const + { + const ESM::LandTexture *ptr = search(index, plugin); + if (ptr == 0) { + std::ostringstream msg; + msg << "Land texture with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + size_t Store::getSize() const + { + return mStatic.size(); + } + size_t Store::getSize(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].size(); + } + RecordId Store::load(ESM::ESMReader &esm, size_t plugin) + { + ESM::LandTexture lt; + bool isDeleted = false; + + lt.load(esm, isDeleted); + + assert(plugin < mStatic.size()); + + LandTextureList <exl = mStatic[plugin]; + if(lt.mIndex + 1 > (int)ltexl.size()) + ltexl.resize(lt.mIndex+1); + + // Store it + ltexl[lt.mIndex] = lt; + + return RecordId(lt.mId, isDeleted); + } + RecordId Store::load(ESM::ESMReader &esm) + { + return load(esm, esm.getIndex()); + } + Store::iterator Store::begin(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].begin(); + } + Store::iterator Store::end(size_t plugin) const + { + assert(plugin < mStatic.size()); + return mStatic[plugin].end(); + } + void Store::resize(size_t num) + { + if (mStatic.size() < num) + mStatic.resize(num); + } + + // Land + //========================================================================= + Store::~Store() + { + for (std::vector::const_iterator it = + mStatic.begin(); it != mStatic.end(); ++it) + { + delete *it; + } + + } + size_t Store::getSize() const + { + return mStatic.size(); + } + Store::iterator Store::begin() const + { + return iterator(mStatic.begin()); + } + Store::iterator Store::end() const + { + return iterator(mStatic.end()); + } + ESM::Land *Store::search(int x, int y) const + { + ESM::Land land; + land.mX = x, land.mY = y; + + std::vector::const_iterator it = + std::lower_bound(mStatic.begin(), mStatic.end(), &land, Compare()); + + if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) { + return const_cast(*it); + } + return 0; + } + ESM::Land *Store::find(int x, int y) const + { + ESM::Land *ptr = search(x, y); + if (ptr == 0) { + std::ostringstream msg; + msg << "Land at (" << x << ", " << y << ") not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + RecordId Store::load(ESM::ESMReader &esm) + { + ESM::Land *ptr = new ESM::Land(); + bool isDeleted = false; + + ptr->load(esm, isDeleted); + + // Same area defined in multiple plugins? -> last plugin wins + // Can't use search() because we aren't sorted yet - is there any other way to speed this up? + for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) + { + delete *it; + mStatic.erase(it); + break; + } + } + + mStatic.push_back(ptr); + + return RecordId("", isDeleted); + } + void Store::setUp() + { + std::sort(mStatic.begin(), mStatic.end(), Compare()); + } + + + // Cell + //========================================================================= + + const ESM::Cell *Store::search(const ESM::Cell &cell) const + { + if (cell.isExterior()) { + return search(cell.getGridX(), cell.getGridY()); + } + return search(cell.mName); + } + void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) + { + //Handling MovedCellRefs, there is no way to do it inside loadcell + while (esm.isNextSub("MVRF")) { + ESM::CellRef ref; + ESM::MovedCellRef cMRef; + cell->getNextMVRF(esm, cMRef); + + ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); + + // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following + // implementation when the oher implementation works as well. + bool deleted = false; + cell->getNextRef(esm, ref, deleted); + + // Add data required to make reference appear in the correct cell. + // We should not need to test for duplicates, as this part of the code is pre-cell merge. + cell->mMovedRefs.push_back(cMRef); + // But there may be duplicates here! + if (!deleted) + { + ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); + if (iter == cellAlt->mLeasedRefs.end()) + cellAlt->mLeasedRefs.push_back(ref); else oldcell->mMovedRefs.push_back(*it); } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index ff0ca01592..5cc66981c1 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -434,9 +434,11 @@ namespace MWWorld assert(plugin < mStatic.size()); const LandTextureList <exl = mStatic[plugin]; - assert(index < ltexl.size()); - return <exl.at(index); - } + /// Resize the internal store to hold at least \a num plugins. + void resize(size_t num); + + size_t getSize() const; + size_t getSize(size_t plugin) const; const ESM::LandTexture *find(size_t index, size_t plugin) const { const ESM::LandTexture *ptr = search(index, plugin); From f456174af8e4452e0d38182520f033e6898bd67f Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 7 Dec 2015 22:49:15 +0100 Subject: [PATCH 4/7] Rename to lowerCaseInPlace (cherry picked from commit 07b064f61682096aef2f5f7838cd064847f5d24e) # Conflicts: # apps/opencs/model/world/commanddispatcher.cpp # apps/opencs/model/world/regionmap.cpp # apps/opencs/model/world/scriptcontext.cpp # apps/openmw/mwrender/animation.cpp # apps/openmw/mwscript/statsextensions.cpp # apps/openmw/mwworld/store.cpp # components/misc/stringops.hpp --- apps/essimporter/converter.hpp | 2 +- apps/mwiniimporter/importer.cpp | 2 +- apps/opencs/model/world/commanddispatcher.cpp | 2 +- apps/opencs/model/world/regionmap.cpp | 2 +- apps/opencs/model/world/scriptcontext.cpp | 2 +- apps/openmw/mwclass/container.cpp | 4 +- apps/openmw/mwclass/door.cpp | 4 +- apps/openmw/mwgui/formatting.cpp | 4 +- apps/openmw/mwgui/itemmodel.cpp | 6 +-- apps/openmw/mwmechanics/activespells.cpp | 8 +--- apps/openmw/mwmechanics/activespells.hpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwrender/animation.cpp | 8 +--- apps/openmw/mwscript/cellextensions.cpp | 2 +- apps/openmw/mwscript/guiextensions.cpp | 4 +- apps/openmw/mwscript/statsextensions.cpp | 20 ++++---- apps/openmw/mwworld/cellstore.cpp | 2 +- apps/openmw/mwworld/globals.cpp | 2 +- apps/openmw/mwworld/store.cpp | 46 ++++++++++++++++++- components/misc/resourcehelpers.cpp | 2 +- components/misc/stringops.hpp | 5 +- 21 files changed, 85 insertions(+), 48 deletions(-) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 61155c1eab..467679e20e 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -395,7 +395,7 @@ public: virtual void read(ESM::ESMReader &esm) { std::string itemid = esm.getHNString("NAME"); - Misc::StringUtils::toLower(itemid); + Misc::StringUtils::lowerCaseInPlace(itemid); while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 479f8cba28..e1534ce61a 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -847,7 +847,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co for(std::vector::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { std::string filetype(entry->substr(entry->length()-3)); - Misc::StringUtils::toLower(filetype); + Misc::StringUtils::lowerCaseInPlace(filetype); if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { boost::filesystem::path filepath(gameFilesDir); diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 0b1af0e840..a1fc980eb5 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -93,7 +93,7 @@ void CSMWorld::CommandDispatcher::setEditLock (bool locked) void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) { mSelection = selection; - std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (mSelection.begin(), mSelection.end()); } diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 10c67c909d..6dbbac97fb 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -168,7 +168,7 @@ void CSMWorld::RegionMap::updateRegions (const std::vector& regions { std::vector regions2 (regions); - std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); + std::for_each (regions2.begin(), regions2.end(), Misc::StringUtils::lowerCaseInPlace); std::sort (regions2.begin(), regions2.end()); for (std::map::const_iterator iter (mMap.begin()); diff --git a/apps/opencs/model/world/scriptcontext.cpp b/apps/opencs/model/world/scriptcontext.cpp index f644ad37ad..344ae322e9 100644 --- a/apps/opencs/model/world/scriptcontext.cpp +++ b/apps/opencs/model/world/scriptcontext.cpp @@ -93,7 +93,7 @@ bool CSMWorld::ScriptContext::isId (const std::string& name) const { mIds = mData.getIds(); - std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::toLower); + std::for_each (mIds.begin(), mIds.end(), &Misc::StringUtils::lowerCaseInPlace); std::sort (mIds.begin(), mIds.end()); mIdsUpdated = true; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 9d2e34ea37..cccb21e8ff 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -146,11 +146,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index ab9cfa2899..83f2680b79 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -113,11 +113,11 @@ namespace MWClass // make key id lowercase std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::toLower(keyId); + Misc::StringUtils::lowerCaseInPlace(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::toLower(refId); + Misc::StringUtils::lowerCaseInPlace(refId); if (refId == keyId) { hasKey = true; diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 761a89ca62..85cef89116 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -129,7 +129,7 @@ namespace MWGui size_t tagNameEndPos = tag.find(' '); mAttributes.clear(); mTag = tag.substr(0, tagNameEndPos); - Misc::StringUtils::toLower(mTag); + Misc::StringUtils::lowerCaseInPlace(mTag); if (mTag.empty()) return; @@ -151,7 +151,7 @@ namespace MWGui return; std::string key = tag.substr(0, sepPos); - Misc::StringUtils::toLower(key); + Misc::StringUtils::lowerCaseInPlace(key); tag.erase(0, sepPos+1); std::string value; diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 9fce6e84dd..64a23c956a 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -35,7 +35,7 @@ namespace MWGui { const ESM::GameSetting ¤tSetting = *currentIteration; std::string currentGMSTID = currentSetting.mId; - Misc::StringUtils::toLower(currentGMSTID); + Misc::StringUtils::lowerCaseInPlace(currentGMSTID); // Don't bother checking this GMST if it's not a sMagicBound* one. const std::string& toFind = "smagicbound"; @@ -44,7 +44,7 @@ namespace MWGui // All sMagicBound* GMST's should be of type string std::string currentGMSTValue = currentSetting.getString(); - Misc::StringUtils::toLower(currentGMSTValue); + Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); boundItemIDCache.insert(currentGMSTValue); } @@ -52,7 +52,7 @@ namespace MWGui // Perform bound item check and assign the Flag_Bound bit if it passes std::string tempItemID = base.getCellRef().getRefId(); - Misc::StringUtils::toLower(tempItemID); + Misc::StringUtils::lowerCaseInPlace(tempItemID); if (boundItemIDCache.count(tempItemID) != 0) mFlags |= Flag_Bound; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 7068310a0d..6a247622c5 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -132,15 +132,11 @@ namespace MWMechanics return scaledDuration-usedUp; } - bool ActiveSpells::isSpellActive(std::string id) const + bool ActiveSpells::isSpellActive(const std::string& id) const { - Misc::StringUtils::toLower(id); for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) { - std::string left = iter->first; - Misc::StringUtils::toLower(left); - - if (iter->first == id) + if (Misc::StringUtils::ciEqual(iter->first, id)) return true; } return false; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 2a4d75d402..3842ee61c5 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -97,7 +97,7 @@ namespace MWMechanics /// Remove all spells void clear(); - bool isSpellActive (std::string id) const; + bool isSpellActive (const std::string& id) const; ///< case insensitive const MagicEffects& getMagicEffects() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2bf23b586c..cc0ddc72a9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -607,7 +607,7 @@ namespace MWMechanics int rank = 0; std::string npcFaction = ptr.getClass().getPrimaryFaction(ptr); - Misc::StringUtils::toLower(npcFaction); + Misc::StringUtils::lowerCaseInPlace(npcFaction); if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { @@ -1045,7 +1045,7 @@ namespace MWMechanics owner.first = ownerCellRef->getFaction(); owner.second = true; } - Misc::StringUtils::toLower(owner.first); + Misc::StringUtils::lowerCaseInPlace(owner.first); if (!Misc::StringUtils::ciEqual(item.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) mStolenItems[Misc::StringUtils::lowerCase(item.getClass().getId(item))][owner] += count; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6dcd92b154..05565ebc3b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -430,12 +430,8 @@ NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::Tex NifOgre::TextKeyMap::const_iterator iter(keys.begin()); for(;iter != keys.end();++iter) { - if(iter->second.compare(0, groupname.size(), groupname) == 0 && - iter->second.compare(groupname.size(), 2, ": ") == 0) - break; - } - return iter; -} + std::string kfname = model; + Misc::StringUtils::lowerCaseInPlace(kfname); bool Animation::hasAnimation(const std::string &anim) diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 43d213c5af..2d69e485f5 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -120,7 +120,7 @@ namespace MWScript const MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); - Misc::StringUtils::toLower(current); + Misc::StringUtils::lowerCaseInPlace(current); bool match = current.length()>=name.length() && current.substr (0, name.length())==name; diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index f48360c047..48b17917e9 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -114,7 +114,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { std::string cell = (runtime.getStringLiteral (runtime[0].mInteger)); - ::Misc::StringUtils::toLower(cell); + ::Misc::StringUtils::lowerCaseInPlace(cell); runtime.pop(); // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." @@ -127,7 +127,7 @@ namespace MWScript for (; it != cells.extEnd(); ++it) { std::string name = it->mName; - ::Misc::StringUtils::toLower(name); + ::Misc::StringUtils::lowerCaseInPlace(name); if (name.find(cell) != std::string::npos) MWBase::Environment::get().getWindowManager()->addVisitedLocation ( it->mName, diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 80f46e4573..875a073743 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -542,7 +542,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -574,7 +574,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -613,7 +613,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -644,7 +644,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); // Make sure this faction exists MWBase::Environment::get().getWorld()->getStore().get().find(factionID); @@ -755,7 +755,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); runtime.push ( @@ -790,7 +790,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats (player).setFactionReputation (factionId, value); @@ -824,7 +824,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - ::Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::lowerCaseInPlace (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats (player).setFactionReputation (factionId, @@ -869,11 +869,11 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); std::string race = runtime.getStringLiteral(runtime[0].mInteger); - ::Misc::StringUtils::toLower(race); + ::Misc::StringUtils::lowerCaseInPlace(race); runtime.pop(); std::string npcRace = ptr.get()->mBase->mRace; - ::Misc::StringUtils::toLower(npcRace); + ::Misc::StringUtils::lowerCaseInPlace(npcRace); runtime.push (npcRace == race); } @@ -910,7 +910,7 @@ namespace MWScript { factionID = ptr.getClass().getPrimaryFaction(ptr); } - ::Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::lowerCaseInPlace(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 67de77ceb6..0e392f0550 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -540,7 +540,7 @@ namespace MWWorld void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) { - Misc::StringUtils::toLower (ref.mRefID); + Misc::StringUtils::lowerCaseInPlace (ref.mRefID); switch (store.find (ref.mRefID)) { diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 4a406613de..acc06b28a9 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -96,7 +96,7 @@ namespace MWWorld // This readRecord() method is used when reading a saved game. // Deleted globals can't appear there, so isDeleted will be ignored here. global.load(reader, isDeleted); - Misc::StringUtils::toLower(global.mId); + Misc::StringUtils::lowerCaseInPlace(global.mId); Collection::iterator iter = mVariables.find (global.mId); if (iter!=mVariables.end()) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 925e9c6053..8fa5b58cbc 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -66,7 +66,51 @@ void Store::load(ESM::ESMReader &esm) // spawn a new cell cell.loadCell(esm, true); - mInt[idLower] = cell; + record.load(esm, isDeleted); + Misc::StringUtils::lowerCaseInPlace(record.mId); + + std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = record; + + return RecordId(record.mId, isDeleted); + } + template + void Store::setUp() + { + } + + template + typename Store::iterator Store::begin() const + { + return mShared.begin(); + } + template + typename Store::iterator Store::end() const + { + return mShared.end(); + } + + template + size_t Store::getSize() const + { + return mShared.size(); + } + + template + int Store::getDynamicSize() const + { + return mDynamic.size(); + } + template + void Store::listIdentifier(std::vector &list) const + { + list.reserve(list.size() + getSize()); + typename std::vector::const_iterator it = mShared.begin(); + for (; it != mShared.end(); ++it) { + list.push_back((*it)->mId); } } else diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index dc08b352a6..1c6834e67a 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -49,7 +49,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLev std::string prefix2 = topLevelDirectory + '/'; std::string correctedPath = resPath; - Misc::StringUtils::toLower(correctedPath); + Misc::StringUtils::lowerCaseInPlace(correctedPath); // Apparently, leading separators are allowed while (correctedPath.size() && (correctedPath[0] == '/' || correctedPath[0] == '\\')) diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 1f6da37c7a..f54018a87f 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -70,7 +70,7 @@ public: } /// Transforms input string to lower case w/o copy - static std::string &toLower(std::string &inout) { + static void lowerCaseInPlace(std::string &inout) { for (unsigned int i=0; i Date: Fri, 5 Oct 2018 21:54:29 +1000 Subject: [PATCH 5/7] Resolved conflicts and build fixes. --- apps/essimporter/converter.hpp | 2 +- apps/openmw/mwclass/static.cpp | 5 +- apps/openmw/mwmechanics/character.cpp | 8 +- apps/openmw/mwrender/animation.cpp | 10 +- apps/openmw/mwrender/sky.cpp | 4 +- apps/openmw/mwworld/store.cpp | 968 ++++++++++++++++++++--- apps/openmw/mwworld/store.hpp | 902 ++++----------------- components/fontloader/fontloader.cpp | 13 +- components/misc/stringops.hpp | 1 - components/nifbullet/bulletnifloader.cpp | 2 +- components/nifogre/ogrenifloader.cpp | 16 +- components/nifogre/particles.cpp | 4 +- libs/openengine/CMakeLists.txt | 2 +- libs/openengine/ogre/selectionbuffer.cpp | 4 +- 14 files changed, 1048 insertions(+), 893 deletions(-) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 467679e20e..a3fef7f324 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -378,7 +378,7 @@ public: bool isDeleted = false; faction.load(esm, isDeleted); - std::string id = Misc::StringUtils::toLower(faction.mId); + std::string id = Misc::StringUtils::lowerCase(faction.mId); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 7bfd4c76d0..a5f8129a2f 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -18,11 +18,8 @@ namespace MWClass void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - MWWorld::LiveCellRef *ref = - ptr.get(); - if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, !ref->mBase->mPersistent); + renderingInterface.getObjects().insertModel(ptr, model); } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 399b51151b..b90228b5b7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -27,7 +27,7 @@ #include "creaturestats.hpp" #include "security.hpp" -#include +#include #include @@ -222,7 +222,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i while (mAnimation->hasAnimation(prefix + Ogre::StringConverter::toString(numAnims+1))) ++numAnims; - int roll = OEngine::Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] + int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] if (num) *num = roll; return prefix + Ogre::StringConverter::toString(roll); @@ -831,7 +831,7 @@ bool CharacterController::updateCreatureState() } if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { - int roll = OEngine::Misc::Rng::rollDice(3); // [0, 2] + int roll = Misc::Rng::rollDice(3); // [0, 2] if (roll == 0) mCurrentWeapon = "attack1"; else if (roll == 1) @@ -1127,7 +1127,7 @@ bool CharacterController::updateWeaponState() // most creatures don't actually have an attack wind-up animation, so use a uniform random value // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + OEngine::Misc::Rng::rollClosedProbability()); + attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 05565ebc3b..8b3f6a4ea5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -249,7 +249,7 @@ void Animation::addAnimSource(const std::string &model) return; std::string kfname = model; - Misc::StringUtils::toLower(kfname); + Misc::StringUtils::lowerCaseInPlace(kfname); if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); @@ -430,8 +430,12 @@ NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::Tex NifOgre::TextKeyMap::const_iterator iter(keys.begin()); for(;iter != keys.end();++iter) { - std::string kfname = model; - Misc::StringUtils::lowerCaseInPlace(kfname); + if(iter->second.compare(0, groupname.size(), groupname) == 0 && + iter->second.compare(groupname.size(), 2, ": ") == 0) + break; + } + return iter; +} bool Animation::hasAnimation(const std::string &anim) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2aab1c0cc2..58362756d8 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -466,8 +466,8 @@ void SkyManager::updateRain(float dt) // TODO: handle rain settings from Morrowind.ini const float rangeRandom = 100; - float xOffs = OEngine::Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); - float yOffs = OEngine::Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); + float xOffs = Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); + float yOffs = Misc::Rng::rollProbability() * rangeRandom - (rangeRandom / 2); // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still // consider the orientation of the parent node for its position, just not for its orientation diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 8fa5b58cbc..adaf8056a2 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -3,68 +3,189 @@ #include -namespace MWWorld { +#include +#include +#include -void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) +namespace { - //Handling MovedCellRefs, there is no way to do it inside loadcell - while (esm.isNextSub("MVRF")) { - ESM::CellRef ref; - ESM::MovedCellRef cMRef; - cell->getNextMVRF(esm, cMRef); + template + class GetRecords + { + const std::string mFind; + std::vector *mRecords; - ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); + public: + GetRecords(const std::string &str, std::vector *records) + : mFind(Misc::StringUtils::lowerCase(str)), mRecords(records) + { } - // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following - // implementation when the oher implementation works as well. - bool deleted = false; - cell->getNextRef(esm, ref, deleted); - - // Add data required to make reference appear in the correct cell. - // We should not need to test for duplicates, as this part of the code is pre-cell merge. - cell->mMovedRefs.push_back(cMRef); - // But there may be duplicates here! - if (!deleted) + void operator()(const T *item) { - ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); - if (iter == cellAlt->mLeasedRefs.end()) - cellAlt->mLeasedRefs.push_back(ref); - else - *iter = ref; + if(Misc::StringUtils::ciCompareLen(mFind, item->mId, mFind.size()) == 0) + mRecords->push_back(item); } - } + }; + + struct Compare + { + bool operator()(const ESM::Land *x, const ESM::Land *y) { + if (x->mX == y->mX) { + return x->mY < y->mY; + } + return x->mX < y->mX; + } + }; } -void Store::load(ESM::ESMReader &esm) +namespace MWWorld { - // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, - // and we merge all this data into one Cell object. However, we can't simply search for the cell id, - // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they - // are not available until both cells have been loaded at least partially! + RecordId::RecordId(const std::string &id, bool isDeleted) + : mId(id), mIsDeleted(isDeleted) + {} - // All cells have a name record, even nameless exterior cells. - ESM::Cell cell; - cell.loadName(esm); - std::string idLower = Misc::StringUtils::lowerCase(cell.mName); - - // Load the (x,y) coordinates of the cell, if it is an exterior cell, - // so we can find the cell we need to merge with - cell.loadData(esm); - - if(cell.mData.mFlags & ESM::Cell::Interior) + template + IndexedStore::IndexedStore() { - // Store interior cell by name, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(idLower)); - if (oldcell) { - // merge new cell into old cell - // push the new references on the list of references to manage (saveContext = true) - oldcell->mData = cell.mData; - oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) - oldcell->loadCell(esm, true); - } else + } + template + typename IndexedStore::iterator IndexedStore::begin() const + { + return mStatic.begin(); + } + template + typename IndexedStore::iterator IndexedStore::end() const + { + return mStatic.end(); + } + template + void IndexedStore::load(ESM::ESMReader &esm) + { + T record; + bool isDeleted = false; + + record.load(esm, isDeleted); + + // Try to overwrite existing record + std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); + if (!ret.second) + ret.first->second = record; + } + template + int IndexedStore::getSize() const + { + return mStatic.size(); + } + template + void IndexedStore::setUp() + { + } + template + const T *IndexedStore::search(int index) const + { + typename Static::const_iterator it = mStatic.find(index); + if (it != mStatic.end()) + return &(it->second); + return NULL; + } + template + const T *IndexedStore::find(int index) const + { + const T *ptr = search(index); + if (ptr == 0) { + std::ostringstream msg; + msg << T::getRecordType() << " with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + // Need to instantiate these before they're used + template class IndexedStore; + template class IndexedStore; + + template + Store::Store() + { + } + + template + Store::Store(const Store& orig) + : mStatic(orig.mStatic) + { + } + + template + void Store::clearDynamic() + { + // remove the dynamic part of mShared + assert(mShared.size() >= mStatic.size()); + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); + mDynamic.clear(); + } + + template + const T *Store::search(const std::string &id) const + { + T item; + item.mId = Misc::StringUtils::lowerCase(id); + + typename Dynamic::const_iterator dit = mDynamic.find(item.mId); + if (dit != mDynamic.end()) { + return &dit->second; + } + + typename std::map::const_iterator it = mStatic.find(item.mId); + + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + return &(it->second); + } + + return 0; + } + template + bool Store::isDynamic(const std::string &id) const + { + typename Dynamic::const_iterator dit = mDynamic.find(id); + return (dit != mDynamic.end()); + } + template + const T *Store::searchRandom(const std::string &id) const + { + std::vector results; + std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); + if(!results.empty()) + return results[Misc::Rng::rollDice(results.size())]; + return NULL; + } + template + const T *Store::find(const std::string &id) const + { + const T *ptr = search(id); + if (ptr == 0) { + std::ostringstream msg; + msg << T::getRecordType() << " '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + template + const T *Store::findRandom(const std::string &id) const + { + const T *ptr = searchRandom(id); + if(ptr == 0) { - // spawn a new cell - cell.loadCell(esm, true); + std::ostringstream msg; + msg << T::getRecordType() << " starting with '"< + RecordId Store::load(ESM::ESMReader &esm) + { + T record; + bool isDeleted = false; record.load(esm, isDeleted); Misc::StringUtils::lowerCaseInPlace(record.mId); @@ -85,7 +206,7 @@ void Store::load(ESM::ESMReader &esm) template typename Store::iterator Store::begin() const { - return mShared.begin(); + return mShared.begin(); } template typename Store::iterator Store::end() const @@ -113,32 +234,51 @@ void Store::load(ESM::ESMReader &esm) list.push_back((*it)->mId); } } - else + template + T *Store::insert(const T &item) { - // Store exterior cells by grid position, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); - if (oldcell) { - // merge new cell into old cell - oldcell->mData = cell.mData; - oldcell->mName = cell.mName; - oldcell->loadCell(esm, false); + std::string id = Misc::StringUtils::lowerCase(item.mId); + std::pair result = + mDynamic.insert(std::pair(id, item)); + T *ptr = &result.first->second; + if (result.second) { + mShared.push_back(ptr); + } else { + *ptr = item; + } + return ptr; + } + template + T *Store::insertStatic(const T &item) + { + std::string id = Misc::StringUtils::lowerCase(item.mId); + std::pair result = + mStatic.insert(std::pair(id, item)); + T *ptr = &result.first->second; + if (result.second) { + mShared.push_back(ptr); + } else { + *ptr = item; + } + return ptr; + } + template + bool Store::eraseStatic(const std::string &id) + { + T item; + item.mId = Misc::StringUtils::lowerCase(id); - // handle moved ref (MVRF) subrecords - handleMovedCellRefs (esm, &cell); + typename std::map::iterator it = mStatic.find(item.mId); - // push the new references on the list of references to manage - oldcell->postLoad(esm); + if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { + // delete from the static part of mShared + typename std::vector::iterator sharedIter = mShared.begin(); + typename std::vector::iterator end = sharedIter + mStatic.size(); - // merge lists of leased references, use newer data in case of conflict - for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { - // remove reference from current leased ref tracker and add it to new cell - ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); - if (itold != oldcell->mMovedRefs.end()) { - ESM::MovedCellRef target0 = *itold; - ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); - ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); - wipecell->mLeasedRefs.erase(it_lease); - *itold = *it; + while (sharedIter != mShared.end() && sharedIter != end) { + if((*sharedIter)->mId == item.mId) { + mShared.erase(sharedIter); + break; } ++sharedIter; } @@ -166,11 +306,13 @@ void Store::load(ESM::ESMReader &esm) } return true; } + template bool Store::erase(const T &item) { return erase(item.mId); } + template void Store::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { @@ -182,6 +324,7 @@ void Store::load(ESM::ESMReader &esm) writer.endRecord (T::sRecordId); } } + template RecordId Store::read(ESM::ESMReader& reader) { @@ -204,6 +347,7 @@ void Store::load(ESM::ESMReader &esm) // added on-the-fly in a different method. ltexl.reserve(128); } + const ESM::LandTexture *Store::search(size_t index, size_t plugin) const { assert(plugin < mStatic.size()); @@ -213,6 +357,7 @@ void Store::load(ESM::ESMReader &esm) return NULL; return <exl[index]; } + const ESM::LandTexture *Store::find(size_t index, size_t plugin) const { const ESM::LandTexture *ptr = search(index, plugin); @@ -223,15 +368,18 @@ void Store::load(ESM::ESMReader &esm) } return ptr; } + size_t Store::getSize() const { return mStatic.size(); } + size_t Store::getSize(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].size(); } + RecordId Store::load(ESM::ESMReader &esm, size_t plugin) { ESM::LandTexture lt; @@ -250,26 +398,30 @@ void Store::load(ESM::ESMReader &esm) return RecordId(lt.mId, isDeleted); } + RecordId Store::load(ESM::ESMReader &esm) { return load(esm, esm.getIndex()); } + Store::iterator Store::begin(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].begin(); } + Store::iterator Store::end(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].end(); } + void Store::resize(size_t num) { if (mStatic.size() < num) mStatic.resize(num); } - + // Land //========================================================================= Store::~Store() @@ -279,20 +431,23 @@ void Store::load(ESM::ESMReader &esm) { delete *it; } - } + size_t Store::getSize() const { return mStatic.size(); } + Store::iterator Store::begin() const { return iterator(mStatic.begin()); } + Store::iterator Store::end() const { return iterator(mStatic.end()); } + ESM::Land *Store::search(int x, int y) const { ESM::Land land; @@ -306,6 +461,7 @@ void Store::load(ESM::ESMReader &esm) } return 0; } + ESM::Land *Store::find(int x, int y) const { ESM::Land *ptr = search(x, y); @@ -316,6 +472,7 @@ void Store::load(ESM::ESMReader &esm) } return ptr; } + RecordId Store::load(ESM::ESMReader &esm) { ESM::Land *ptr = new ESM::Land(); @@ -339,12 +496,12 @@ void Store::load(ESM::ESMReader &esm) return RecordId("", isDeleted); } + void Store::setUp() { std::sort(mStatic.begin(), mStatic.end(), Compare()); } - // Cell //========================================================================= @@ -355,6 +512,7 @@ void Store::load(ESM::ESMReader &esm) } return search(cell.mName); } + void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { //Handling MovedCellRefs, there is no way to do it inside loadcell @@ -380,31 +538,643 @@ void Store::load(ESM::ESMReader &esm) if (iter == cellAlt->mLeasedRefs.end()) cellAlt->mLeasedRefs.push_back(ref); else - oldcell->mMovedRefs.push_back(*it); + *iter = ref; } - - // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a - // reference to this cell, so the list for the new cell should be empty. The list for oldcell, - // however, could have leased refs in it and so should be kept. - } else - { - // spawn a new cell - cell.loadCell(esm, false); - - // handle moved ref (MVRF) subrecords - handleMovedCellRefs (esm, &cell); - - // push the new references on the list of references to manage - cell.postLoad(esm); - - mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; } } + + const ESM::Cell *Store::search(const std::string &id) const + { + ESM::Cell cell; + cell.mName = Misc::StringUtils::lowerCase(id); + + std::map::const_iterator it = mInt.find(cell.mName); + + if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { + return &(it->second); + } + + DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); + if (dit != mDynamicInt.end()) { + return &dit->second; + } + + return 0; + } + + const ESM::Cell *Store::search(int x, int y) const + { + ESM::Cell cell; + cell.mData.mX = x, cell.mData.mY = y; + + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + + DynamicExt::const_iterator dit = mDynamicExt.find(key); + if (dit != mDynamicExt.end()) { + return &dit->second; + } + + return 0; + } + + const ESM::Cell *Store::searchOrCreate(int x, int y) + { + std::pair key(x, y); + DynamicExt::const_iterator it = mExt.find(key); + if (it != mExt.end()) { + return &(it->second); + } + + DynamicExt::const_iterator dit = mDynamicExt.find(key); + if (dit != mDynamicExt.end()) { + return &dit->second; + } + + ESM::Cell newCell; + newCell.mData.mX = x; + newCell.mData.mY = y; + newCell.mData.mFlags = ESM::Cell::HasWater; + newCell.mAmbi.mAmbient = 0; + newCell.mAmbi.mSunlight = 0; + newCell.mAmbi.mFog = 0; + newCell.mAmbi.mFogDensity = 0; + return &mExt.insert(std::make_pair(key, newCell)).first->second; + } + + const ESM::Cell *Store::find(const std::string &id) const + { + const ESM::Cell *ptr = search(id); + if (ptr == 0) { + std::ostringstream msg; + msg << "Interior cell '" << id << "' not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + const ESM::Cell *Store::find(int x, int y) const + { + const ESM::Cell *ptr = search(x, y); + if (ptr == 0) { + std::ostringstream msg; + msg << "Exterior at (" << x << ", " << y << ") not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + void Store::setUp() + { + typedef DynamicExt::iterator ExtIterator; + typedef std::map::iterator IntIterator; + + mSharedInt.clear(); + mSharedInt.reserve(mInt.size()); + for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { + mSharedInt.push_back(&(it->second)); + } + + mSharedExt.clear(); + mSharedExt.reserve(mExt.size()); + for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { + mSharedExt.push_back(&(it->second)); + } + } + + RecordId Store::load(ESM::ESMReader &esm) + { + // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, + // and we merge all this data into one Cell object. However, we can't simply search for the cell id, + // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they + // are not available until both cells have been loaded at least partially! + + // All cells have a name record, even nameless exterior cells. + ESM::Cell cell; + bool isDeleted = false; + + // Load the (x,y) coordinates of the cell, if it is an exterior cell, + // so we can find the cell we need to merge with + cell.loadNameAndData(esm, isDeleted); + std::string idLower = Misc::StringUtils::lowerCase(cell.mName); + + if(cell.mData.mFlags & ESM::Cell::Interior) + { + // Store interior cell by name, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(idLower)); + if (oldcell) { + // merge new cell into old cell + // push the new references on the list of references to manage (saveContext = true) + oldcell->mData = cell.mData; + oldcell->mName = cell.mName; // merge name just to be sure (ID will be the same, but case could have been changed) + oldcell->loadCell(esm, true); + } else + { + // spawn a new cell + cell.loadCell(esm, true); + + mInt[idLower] = cell; + } + } + else + { + // Store exterior cells by grid position, try to merge with existing parent data. + ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); + if (oldcell) { + // merge new cell into old cell + oldcell->mData = cell.mData; + oldcell->mName = cell.mName; + oldcell->loadCell(esm, false); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + + // push the new references on the list of references to manage + oldcell->postLoad(esm); + + // merge lists of leased references, use newer data in case of conflict + for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { + // remove reference from current leased ref tracker and add it to new cell + ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); + if (itold != oldcell->mMovedRefs.end()) { + ESM::MovedCellRef target0 = *itold; + ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); + ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); + wipecell->mLeasedRefs.erase(it_lease); + *itold = *it; + } + else + oldcell->mMovedRefs.push_back(*it); + } + + // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a + // reference to this cell, so the list for the new cell should be empty. The list for oldcell, + // however, could have leased refs in it and so should be kept. + } else + { + // spawn a new cell + cell.loadCell(esm, false); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + + // push the new references on the list of references to manage + cell.postLoad(esm); + + mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; + } + } + + return RecordId(cell.mName, isDeleted); + } + + Store::iterator Store::intBegin() const + { + return iterator(mSharedInt.begin()); + } + + Store::iterator Store::intEnd() const + { + return iterator(mSharedInt.end()); + } + + Store::iterator Store::extBegin() const + { + return iterator(mSharedExt.begin()); + } + + Store::iterator Store::extEnd() const + { + return iterator(mSharedExt.end()); + } + + const ESM::Cell *Store::searchExtByName(const std::string &id) const + { + ESM::Cell *cell = 0; + std::vector::const_iterator it = mSharedExt.begin(); + for (; it != mSharedExt.end(); ++it) { + if (Misc::StringUtils::ciEqual((*it)->mName, id)) { + if ( cell == 0 || + ( (*it)->mData.mX > cell->mData.mX ) || + ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + { + cell = *it; + } + } + } + return cell; + } + + const ESM::Cell *Store::searchExtByRegion(const std::string &id) const + { + ESM::Cell *cell = 0; + std::vector::const_iterator it = mSharedExt.begin(); + for (; it != mSharedExt.end(); ++it) { + if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { + if ( cell == 0 || + ( (*it)->mData.mX > cell->mData.mX ) || + ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) + { + cell = *it; + } + } + } + return cell; + } + + size_t Store::getSize() const + { + return mSharedInt.size() + mSharedExt.size(); + } + + void Store::listIdentifier(std::vector &list) const + { + list.reserve(list.size() + mSharedInt.size()); + + std::vector::const_iterator it = mSharedInt.begin(); + for (; it != mSharedInt.end(); ++it) { + list.push_back((*it)->mName); + } + } + + ESM::Cell *Store::insert(const ESM::Cell &cell) + { + if (search(cell) != 0) { + std::ostringstream msg; + msg << "Failed to create "; + msg << ((cell.isExterior()) ? "exterior" : "interior"); + msg << " cell"; + + throw std::runtime_error(msg.str()); + } + ESM::Cell *ptr; + if (cell.isExterior()) { + std::pair key(cell.getGridX(), cell.getGridY()); + + // duplicate insertions are avoided by search(ESM::Cell &) + std::pair result = + mDynamicExt.insert(std::make_pair(key, cell)); + + ptr = &result.first->second; + mSharedExt.push_back(ptr); + } else { + std::string key = Misc::StringUtils::lowerCase(cell.mName); + + // duplicate insertions are avoided by search(ESM::Cell &) + std::pair result = + mDynamicInt.insert(std::make_pair(key, cell)); + + ptr = &result.first->second; + mSharedInt.push_back(ptr); + } + return ptr; + } + + bool Store::erase(const ESM::Cell &cell) + { + if (cell.isExterior()) { + return erase(cell.getGridX(), cell.getGridY()); + } + return erase(cell.mName); + } + + bool Store::erase(const std::string &id) + { + std::string key = Misc::StringUtils::lowerCase(id); + DynamicInt::iterator it = mDynamicInt.find(key); + + if (it == mDynamicInt.end()) { + return false; + } + mDynamicInt.erase(it); + mSharedInt.erase( + mSharedInt.begin() + mSharedInt.size(), + mSharedInt.end() + ); + + for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) { + mSharedInt.push_back(&it->second); + } + + return true; + } + + bool Store::erase(int x, int y) + { + std::pair key(x, y); + DynamicExt::iterator it = mDynamicExt.find(key); + + if (it == mDynamicExt.end()) { + return false; + } + mDynamicExt.erase(it); + mSharedExt.erase( + mSharedExt.begin() + mSharedExt.size(), + mSharedExt.end() + ); + + for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) { + mSharedExt.push_back(&it->second); + } + + return true; + } + + // Pathgrid + //========================================================================= + + Store::Store() + : mCells(NULL) + { + } + + void Store::setCells(Store& cells) + { + mCells = &cells; + } + RecordId Store::load(ESM::ESMReader &esm) + { + ESM::Pathgrid pathgrid; + bool isDeleted = false; + + pathgrid.load(esm, isDeleted); + + // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. + // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. + // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. + // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. + // A proper fix should be made for future versions of the file format. + bool interior = mCells->search(pathgrid.mCell) != NULL; + + // Try to overwrite existing record + if (interior) + { + std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } + else + { + std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); + if (!ret.second) + ret.first->second = pathgrid; + } + + return RecordId("", isDeleted); + } + size_t Store::getSize() const + { + return mInt.size() + mExt.size(); + } + void Store::setUp() + { + } + const ESM::Pathgrid *Store::search(int x, int y) const + { + Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); + if (it != mExt.end()) + return &(it->second); + return NULL; + } + const ESM::Pathgrid *Store::search(const std::string& name) const + { + Interior::const_iterator it = mInt.find(name); + if (it != mInt.end()) + return &(it->second); + return NULL; + } + const ESM::Pathgrid *Store::find(int x, int y) const + { + const ESM::Pathgrid* pathgrid = search(x,y); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << x << " " << y << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + const ESM::Pathgrid* Store::find(const std::string& name) const + { + const ESM::Pathgrid* pathgrid = search(name); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << name << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + const ESM::Pathgrid *Store::search(const ESM::Cell &cell) const + { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return search(cell.mData.mX, cell.mData.mY); + else + return search(cell.mName); + } + const ESM::Pathgrid *Store::find(const ESM::Cell &cell) const + { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return find(cell.mData.mX, cell.mData.mY); + else + return find(cell.mName); + } + + // Skill + //========================================================================= + + Store::Store() + { + } + + // Magic effect + //========================================================================= + + Store::Store() + { + } + + // Attribute + //========================================================================= + + Store::Store() + { + mStatic.reserve(ESM::Attribute::Length); + } + const ESM::Attribute *Store::search(size_t index) const + { + if (index >= mStatic.size()) { + return 0; + } + return &mStatic.at(index); + } + + const ESM::Attribute *Store::find(size_t index) const + { + const ESM::Attribute *ptr = search(index); + if (ptr == 0) { + std::ostringstream msg; + msg << "Attribute with index " << index << " not found"; + throw std::runtime_error(msg.str()); + } + return ptr; + } + + void Store::setUp() + { + for (int i = 0; i < ESM::Attribute::Length; ++i) { + mStatic.push_back( + ESM::Attribute( + ESM::Attribute::sAttributeIds[i], + ESM::Attribute::sGmstAttributeIds[i], + ESM::Attribute::sGmstAttributeDescIds[i] + ) + ); + } + } + + size_t Store::getSize() const + { + return mStatic.size(); + } + + Store::iterator Store::begin() const + { + return mStatic.begin(); + } + + Store::iterator Store::end() const + { + return mStatic.end(); + } + + // Dialogue + //========================================================================= + + template<> + void Store::setUp() + { + // DialInfos marked as deleted are kept during the loading phase, so that the linked list + // structure is kept intact for inserting further INFOs. Delete them now that loading is done. + for (Static::iterator it = mStatic.begin(); it != mStatic.end(); ++it) + { + ESM::Dialogue& dial = it->second; + dial.clearDeletedInfos(); + } + + mShared.clear(); + mShared.reserve(mStatic.size()); + std::map::iterator it = mStatic.begin(); + for (; it != mStatic.end(); ++it) { + mShared.push_back(&(it->second)); + } + } + + template <> + inline RecordId Store::load(ESM::ESMReader &esm) { + // The original letter case of a dialogue ID is saved, because it's printed + ESM::Dialogue dialogue; + bool isDeleted = false; + + dialogue.loadId(esm); + + std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); + std::map::iterator found = mStatic.find(idLower); + if (found == mStatic.end()) + { + dialogue.loadData(esm, isDeleted); + mStatic.insert(std::make_pair(idLower, dialogue)); + } + else + { + found->second.loadData(esm, isDeleted); + dialogue = found->second; + } + + return RecordId(dialogue.mId, isDeleted); + } +#if 0 + // Script + //========================================================================= + + template <> + inline RecordId Store::load(ESM::ESMReader &esm) { + ESM::Script script; + script.load(esm); + Misc::StringUtils::toLower(script.mId); + + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = script; + + return RecordId(script.mId, script.mIsDeleted); + } + + // StartScript + //========================================================================= + + template <> + inline RecordId Store::load(ESM::ESMReader &esm) + { + ESM::StartScript script; + script.load(esm); + Misc::StringUtils::toLower(script.mId); + + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = script; + + return RecordId(script.mId); + } +#endif } -void Store::load(ESM::ESMReader &esm) -{ - load(esm, esm.getIndex()); -} - -} +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +//template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; +template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 5cc66981c1..b51ad9272a 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include @@ -18,23 +16,34 @@ namespace MWWorld { - struct StoreBase + struct RecordId { + std::string mId; + bool mIsDeleted; + + RecordId(const std::string &id = "", bool isDeleted = false); + }; + + class StoreBase + { + public: virtual ~StoreBase() {} virtual void setUp() {} + + /// List identifiers of records contained in this Store (case-smashed). No-op for Stores that don't use string IDs. virtual void listIdentifier(std::vector &list) const {} virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm) = 0; + virtual RecordId load(ESM::ESMReader &esm) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual void read (ESM::ESMReader& reader) {} + virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); } ///< Read into dynamic storage virtual std::string getLastAddedRecordId() const { return ""; } @@ -42,6 +51,30 @@ namespace MWWorld virtual bool isLastAddedRecordDeleted() const { return false; } }; + template + class IndexedStore + { + protected: + typedef typename std::map Static; + Static mStatic; + + public: + typedef typename std::map::const_iterator iterator; + + IndexedStore(); + + iterator begin() const; + iterator end() const; + + void load(ESM::ESMReader &esm); + + int getSize() const; + void setUp(); + + const T *search(int index) const; + const T *find(int index) const; + }; + template class SharedIterator { @@ -136,216 +169,51 @@ namespace MWWorld friend class ESMStore; public: - Store() - {} - - Store(const Store &orig) - : mStatic(orig.mData) - {} + Store(); + Store(const Store &orig); typedef SharedIterator iterator; // setUp needs to be called again after - virtual void clearDynamic() - { - // remove the dynamic part of mShared - assert(mShared.size() >= mStatic.size()); - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - mDynamic.clear(); - } + virtual void clearDynamic(); + void setUp(); - const T *search(const std::string &id) const { - T item; - item.mId = Misc::StringUtils::lowerCase(id); - - typename Dynamic::const_iterator dit = mDynamic.find(item.mId); - if (dit != mDynamic.end()) { - return &dit->second; - } - - typename std::map::const_iterator it = mStatic.find(item.mId); - - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { - return &(it->second); - } - - return 0; - } + const T *search(const std::string &id) const; /** * Does the record with this ID come from the dynamic store? */ - bool isDynamic(const std::string &id) const { - typename Dynamic::const_iterator dit = mDynamic.find(id); - return (dit != mDynamic.end()); - } + bool isDynamic(const std::string &id) const; /** Returns a random record that starts with the named ID, or NULL if not found. */ - const T *searchRandom(const std::string &id) const - { - std::vector results; - std::for_each(mShared.begin(), mShared.end(), GetRecords(id, &results)); - if(!results.empty()) - return results[Misc::Rng::rollDice(results.size())]; - return NULL; - } + const T *searchRandom(const std::string &id) const; - const T *find(const std::string &id) const { - const T *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " '" << id << "' not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + const T *find(const std::string &id) const; /** Returns a random record that starts with the named ID. An exception is thrown if none * are found. */ - const T *findRandom(const std::string &id) const - { - const T *ptr = searchRandom(id); - if(ptr == 0) - { - std::ostringstream msg; - msg << T::getRecordType() << " starting with '"< inserted = mStatic.insert(std::make_pair(record.mId, record)); - if (inserted.second) - mShared.push_back(&inserted.first->second); + size_t getSize() const; + int getDynamicSize() const; - mLastAddedRecord = record; - } + /// @note The record identifiers are listed in the order that the records were defined by the content files. + void listIdentifier(std::vector &list) const; - void setUp() { - } + T *insert(const T &item); + T *insertStatic(const T &item); - iterator begin() const { - return mShared.begin(); - } - - iterator end() const { - return mShared.end(); - } - - size_t getSize() const { - return mShared.size(); - } - - int getDynamicSize() const - { - return static_cast (mDynamic.size()); // truncated from unsigned __int64 if _MSC_VER && _WIN64 - } - - void listIdentifier(std::vector &list) const { - list.reserve(list.size() + getSize()); - typename std::vector::const_iterator it = mShared.begin(); - for (; it != mShared.end(); ++it) { - list.push_back((*it)->mId); - } - } - - T *insert(const T &item) { - std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = - mDynamic.insert(std::pair(id, item)); - T *ptr = &result.first->second; - if (result.second) { - mShared.push_back(ptr); - } else { - *ptr = item; - } - return ptr; - } - - T *insertStatic(const T &item) { - std::string id = Misc::StringUtils::lowerCase(item.mId); - std::pair result = - mStatic.insert(std::pair(id, item)); - T *ptr = &result.first->second; - if (result.second) { - mShared.push_back(ptr); - } else { - *ptr = item; - } - return ptr; - } - - - bool eraseStatic(const std::string &id) { - T item; - item.mId = Misc::StringUtils::lowerCase(id); - - typename std::map::iterator it = mStatic.find(item.mId); - - if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { - // delete from the static part of mShared - typename std::vector::iterator sharedIter = mShared.begin(); - typename std::vector::iterator end = sharedIter + mStatic.size(); - - while (sharedIter != mShared.end() && sharedIter != end) { - if((*sharedIter)->mId == item.mId) { - mShared.erase(sharedIter); - break; - } - ++sharedIter; - } - mStatic.erase(it); - } - - return true; - } - - bool erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - typename Dynamic::iterator it = mDynamic.find(key); - if (it == mDynamic.end()) { - return false; - } - mDynamic.erase(it); - - // have to reinit the whole shared part - assert(mShared.size() >= mStatic.size()); - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { - mShared.push_back(&it->second); - } - return true; - } - - bool erase(const T &item) { - return erase(item.mId); - } - - void write (ESM::ESMWriter& writer, Loading::Listener& progress) const - { - for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); - ++iter) - { - writer.startRecord (T::sRecordId); - iter->second.save (writer); - writer.endRecord (T::sRecordId); - } - } - - void read (ESM::ESMReader& reader) - { - T record; - record.load (reader); - insert (record); - - mLastAddedRecord = record; - } + bool eraseStatic(const std::string &id); + bool erase(const std::string &id); + bool erase(const T &item); + RecordId load(ESM::ESMReader &esm); + void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; + RecordId read(ESM::ESMReader& reader); +#if 0 std::string getLastAddedRecordId() const { return ESM::getRecordId(mLastAddedRecord); @@ -355,13 +223,16 @@ namespace MWWorld { return ESM::isRecordDeleted(mLastAddedRecord); } +#endif }; +#if 0 template <> inline void Store::load(ESM::ESMReader &esm) { // The original letter case of a dialogue ID is saved, because it's printed ESM::Dialogue dialogue; - dialogue.load(esm); + bool isDeleted; + dialogue.load(esm, isDeleted); std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); std::map::iterator found = mStatic.find(idLower); @@ -371,17 +242,18 @@ namespace MWWorld } else { - found->second.mIsDeleted = dialogue.mIsDeleted; + //found->second.mIsDeleted = mIsDeleted; found->second.mType = dialogue.mType; } - + mLastAddedRecord = dialogue; } template <> inline void Store::load(ESM::ESMReader &esm) { ESM::Script script; - script.load(esm); + bool isDeleted; + script.load(esm, isDeleted); Misc::StringUtils::toLower(script.mId); std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); @@ -389,7 +261,7 @@ namespace MWWorld mShared.push_back(&inserted.first->second); else inserted.first->second = script; - + mLastAddedRecord = script; } @@ -397,7 +269,8 @@ namespace MWWorld inline void Store::load(ESM::ESMReader &esm) { ESM::StartScript script; - script.load(esm); + bool isDeleted; + script.load(esm, isDeleted); Misc::StringUtils::toLower(script.mId); std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); @@ -405,9 +278,10 @@ namespace MWWorld mShared.push_back(&inserted.first->second); else inserted.first->second = script; - + mLastAddedRecord = script; } +#endif template <> class Store : public StoreBase @@ -415,24 +289,17 @@ namespace MWWorld // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; std::vector mStatic; - ESM::LandTexture mLastLoadedTexture; + //ESM::LandTexture mLastLoadedTexture; public: - Store() { - mStatic.push_back(LandTextureList()); - LandTextureList <exl = mStatic[0]; - // More than enough to hold Morrowind.esm. Extra lists for plugins will we - // added on-the-fly in a different method. - ltexl.reserve(128); - } + Store(); typedef std::vector::const_iterator iterator; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased - const ESM::LandTexture *search(size_t index, size_t plugin) const { - assert(plugin < mStatic.size()); - const LandTextureList <exl = mStatic[plugin]; + const ESM::LandTexture *search(size_t index, size_t plugin) const; + const ESM::LandTexture *find(size_t index, size_t plugin) const; /// Resize the internal store to hold at least \a num plugins. void resize(size_t num); @@ -440,62 +307,22 @@ namespace MWWorld size_t getSize() const; size_t getSize(size_t plugin) const; - const ESM::LandTexture *find(size_t index, size_t plugin) const { - const ESM::LandTexture *ptr = search(index, plugin); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land texture with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - size_t getSize() const { - return mStatic.size(); - } - - size_t getSize(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].size(); - } - - void load(ESM::ESMReader &esm, size_t plugin) { - ESM::LandTexture lt; - lt.load(esm); - - // Make sure we have room for the structure - if (plugin >= mStatic.size()) { - mStatic.resize(plugin+1); - } - LandTextureList <exl = mStatic[plugin]; - if(lt.mIndex + 1 > (int)ltexl.size()) - ltexl.resize(lt.mIndex+1); - - // Store it - ltexl[lt.mIndex] = lt; - } - - void load(ESM::ESMReader &esm); - - iterator begin(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].begin(); - } - - iterator end(size_t plugin) const { - assert(plugin < mStatic.size()); - return mStatic[plugin].end(); - } + RecordId load(ESM::ESMReader &esm, size_t plugin); + RecordId load(ESM::ESMReader &esm); + iterator begin(size_t plugin) const; + iterator end(size_t plugin) const; +#if 0 std::string getLastAddedRecordId() const { - return ESM::getRecordId(mLastLoadedTexture); + return "";// ESM::getRecordId(mLastLoadedTexture); } bool isLastAddedRecordDeleted() const { - return ESM::isRecordDeleted(mLastLoadedTexture); + return 0;// ESM::isRecordDeleted(mLastLoadedTexture); } +#endif }; template <> @@ -503,88 +330,22 @@ namespace MWWorld { std::vector mStatic; - struct Compare - { - bool operator()(const ESM::Land *x, const ESM::Land *y) { - if (x->mX == y->mX) { - return x->mY < y->mY; - } - return x->mX < y->mX; - } - }; - public: typedef SharedIterator iterator; - virtual ~Store() - { - for (std::vector::const_iterator it = - mStatic.begin(); it != mStatic.end(); ++it) - { - delete *it; - } + virtual ~Store(); - } - - size_t getSize() const { - return mStatic.size(); - } - - iterator begin() const { - return iterator(mStatic.begin()); - } - - iterator end() const { - return iterator(mStatic.end()); - } + size_t getSize() const; + iterator begin() const; + iterator end() const; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::Land can never be modified or inserted/erased - ESM::Land *search(int x, int y) const { - ESM::Land land; - land.mX = x, land.mY = y; + ESM::Land *search(int x, int y) const; + ESM::Land *find(int x, int y) const; - std::vector::const_iterator it = - std::lower_bound(mStatic.begin(), mStatic.end(), &land, Compare()); - - if (it != mStatic.end() && (*it)->mX == x && (*it)->mY == y) { - return const_cast(*it); - } - return 0; - } - - ESM::Land *find(int x, int y) const{ - ESM::Land *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Land at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - void load(ESM::ESMReader &esm) { - ESM::Land *ptr = new ESM::Land(); - ptr->load(esm); - - // Same area defined in multiple plugins? -> last plugin wins - // Can't use search() because we aren't sorted yet - is there any other way to speed this up? - for (std::vector::iterator it = mStatic.begin(); it != mStatic.end(); ++it) - { - if ((*it)->mX == ptr->mX && (*it)->mY == ptr->mY) - { - delete *it; - mStatic.erase(it); - break; - } - } - - mStatic.push_back(ptr); - } - - void setUp() { - std::sort(mStatic.begin(), mStatic.end(), Compare()); - } + RecordId load(ESM::ESMReader &esm); + void setUp(); }; template <> @@ -618,261 +379,44 @@ namespace MWWorld DynamicInt mDynamicInt; DynamicExt mDynamicExt; - const ESM::Cell *search(const ESM::Cell &cell) const { - if (cell.isExterior()) { - return search(cell.getGridX(), cell.getGridY()); - } - return search(cell.mName); - } - + const ESM::Cell *search(const ESM::Cell &cell) const; void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: typedef SharedIterator iterator; - const ESM::Cell *search(const std::string &id) const { - ESM::Cell cell; - cell.mName = Misc::StringUtils::lowerCase(id); + const ESM::Cell *search(const std::string &id) const; + const ESM::Cell *search(int x, int y) const; + const ESM::Cell *searchOrCreate(int x, int y); - std::map::const_iterator it = mInt.find(cell.mName); + const ESM::Cell *find(const std::string &id) const; + const ESM::Cell *find(int x, int y) const; - if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { - return &(it->second); - } + void setUp(); - DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); - if (dit != mDynamicInt.end()) { - return &dit->second; - } + RecordId load(ESM::ESMReader &esm); - return 0; - } - - const ESM::Cell *search(int x, int y) const { - ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; - - std::pair key(x, y); - DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { - return &(it->second); - } - - DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { - return &dit->second; - } - - return 0; - } - - const ESM::Cell *searchOrCreate(int x, int y) { - std::pair key(x, y); - DynamicExt::const_iterator it = mExt.find(key); - if (it != mExt.end()) { - return &(it->second); - } - - DynamicExt::const_iterator dit = mDynamicExt.find(key); - if (dit != mDynamicExt.end()) { - return &dit->second; - } - - ESM::Cell newCell; - newCell.mData.mX = x; - newCell.mData.mY = y; - newCell.mData.mFlags = ESM::Cell::HasWater; - newCell.mAmbi.mAmbient = 0; - newCell.mAmbi.mSunlight = 0; - newCell.mAmbi.mFog = 0; - newCell.mAmbi.mFogDensity = 0; - return &mExt.insert(std::make_pair(key, newCell)).first->second; - } - - const ESM::Cell *find(const std::string &id) const { - const ESM::Cell *ptr = search(id); - if (ptr == 0) { - std::ostringstream msg; - msg << "Interior cell '" << id << "' not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - const ESM::Cell *find(int x, int y) const { - const ESM::Cell *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Exterior at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - void setUp() { - typedef DynamicExt::iterator ExtIterator; - typedef std::map::iterator IntIterator; - - mSharedInt.clear(); - mSharedInt.reserve(mInt.size()); - for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { - mSharedInt.push_back(&(it->second)); - } - - mSharedExt.clear(); - mSharedExt.reserve(mExt.size()); - for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { - mSharedExt.push_back(&(it->second)); - } - } - - // HACK: Method implementation had to be moved to a separate cpp file, as we would otherwise get - // errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find. - // There some nasty three-way cyclic header dependency involved, which I could only fix by moving - // this method. - void load(ESM::ESMReader &esm); - - iterator intBegin() const { - return iterator(mSharedInt.begin()); - } - - iterator intEnd() const { - return iterator(mSharedInt.end()); - } - - iterator extBegin() const { - return iterator(mSharedExt.begin()); - } - - iterator extEnd() const { - return iterator(mSharedExt.end()); - } + iterator intBegin() const; + iterator intEnd() const; + iterator extBegin() const; + iterator extEnd() const; // Return the northernmost cell in the easternmost column. - const ESM::Cell *searchExtByName(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mName, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) - { - cell = *it; - } - } - } - return cell; - } + const ESM::Cell *searchExtByName(const std::string &id) const; // Return the northernmost cell in the easternmost column. - const ESM::Cell *searchExtByRegion(const std::string &id) const { - ESM::Cell *cell = 0; - std::vector::const_iterator it = mSharedExt.begin(); - for (; it != mSharedExt.end(); ++it) { - if (Misc::StringUtils::ciEqual((*it)->mRegion, id)) { - if ( cell == 0 || - ( (*it)->mData.mX > cell->mData.mX ) || - ( (*it)->mData.mX == cell->mData.mX && (*it)->mData.mY > cell->mData.mY ) ) - { - cell = *it; - } - } - } - return cell; - } + const ESM::Cell *searchExtByRegion(const std::string &id) const; - size_t getSize() const { - return mSharedInt.size() + mSharedExt.size(); - } + size_t getSize() const; - void listIdentifier(std::vector &list) const { - list.reserve(list.size() + mSharedInt.size()); + void listIdentifier(std::vector &list) const; - std::vector::const_iterator it = mSharedInt.begin(); - for (; it != mSharedInt.end(); ++it) { - list.push_back((*it)->mName); - } - } + ESM::Cell *insert(const ESM::Cell &cell); - ESM::Cell *insert(const ESM::Cell &cell) { - if (search(cell) != 0) { - std::ostringstream msg; - msg << "Failed to create "; - msg << ((cell.isExterior()) ? "exterior" : "interior"); - msg << " cell"; + bool erase(const ESM::Cell &cell); + bool erase(const std::string &id); - throw std::runtime_error(msg.str()); - } - ESM::Cell *ptr; - if (cell.isExterior()) { - std::pair key(cell.getGridX(), cell.getGridY()); - - // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicExt.insert(std::make_pair(key, cell)); - - ptr = &result.first->second; - mSharedExt.push_back(ptr); - } else { - std::string key = Misc::StringUtils::lowerCase(cell.mName); - - // duplicate insertions are avoided by search(ESM::Cell &) - std::pair result = - mDynamicInt.insert(std::make_pair(key, cell)); - - ptr = &result.first->second; - mSharedInt.push_back(ptr); - } - return ptr; - } - - bool erase(const ESM::Cell &cell) { - if (cell.isExterior()) { - return erase(cell.getGridX(), cell.getGridY()); - } - return erase(cell.mName); - } - - bool erase(const std::string &id) { - std::string key = Misc::StringUtils::lowerCase(id); - DynamicInt::iterator it = mDynamicInt.find(key); - - if (it == mDynamicInt.end()) { - return false; - } - mDynamicInt.erase(it); - mSharedInt.erase( - mSharedInt.begin() + mSharedInt.size(), - mSharedInt.end() - ); - - for (it = mDynamicInt.begin(); it != mDynamicInt.end(); ++it) { - mSharedInt.push_back(&it->second); - } - - return true; - } - - bool erase(int x, int y) { - std::pair key(x, y); - DynamicExt::iterator it = mDynamicExt.find(key); - - if (it == mDynamicExt.end()) { - return false; - } - mDynamicExt.erase(it); - mSharedExt.erase( - mSharedExt.begin() + mSharedExt.size(), - mSharedExt.end() - ); - - for (it = mDynamicExt.begin(); it != mDynamicExt.end(); ++it) { - mSharedExt.push_back(&it->second); - } - - return true; - } + bool erase(int x, int y); }; template <> @@ -889,165 +433,35 @@ namespace MWWorld public: - Store() - : mCells(NULL) - { - } + Store(); - void setCells(Store& cells) - { - mCells = &cells; - } + void setCells(Store& cells); + RecordId load(ESM::ESMReader &esm); + size_t getSize() const; - void load(ESM::ESMReader &esm) { - ESM::Pathgrid pathgrid; - pathgrid.load(esm); + void setUp(); - // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. - // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. - // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. - // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. - // A proper fix should be made for future versions of the file format. - bool interior = mCells->search(pathgrid.mCell) != NULL; - - // Try to overwrite existing record - if (interior) - { - std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } - else - { - std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); - if (!ret.second) - ret.first->second = pathgrid; - } - } - - size_t getSize() const { - return mInt.size() + mExt.size(); - } - - void setUp() { - } - - const ESM::Pathgrid *search(int x, int y) const { - Exterior::const_iterator it = mExt.find(std::make_pair(x,y)); - if (it != mExt.end()) - return &(it->second); - return NULL; - } - - const ESM::Pathgrid *search(const std::string& name) const { - Interior::const_iterator it = mInt.find(name); - if (it != mInt.end()) - return &(it->second); - return NULL; - } - - const ESM::Pathgrid *find(int x, int y) const { - const ESM::Pathgrid* pathgrid = search(x,y); - if (!pathgrid) - { - std::ostringstream msg; - msg << "Pathgrid in cell '" << x << " " << y << "' not found"; - throw std::runtime_error(msg.str()); - } - return pathgrid; - } - - const ESM::Pathgrid* find(const std::string& name) const { - const ESM::Pathgrid* pathgrid = search(name); - if (!pathgrid) - { - std::ostringstream msg; - msg << "Pathgrid in cell '" << name << "' not found"; - throw std::runtime_error(msg.str()); - } - return pathgrid; - } - - const ESM::Pathgrid *search(const ESM::Cell &cell) const { - if (!(cell.mData.mFlags & ESM::Cell::Interior)) - return search(cell.mData.mX, cell.mData.mY); - else - return search(cell.mName); - } - - const ESM::Pathgrid *find(const ESM::Cell &cell) const { - if (!(cell.mData.mFlags & ESM::Cell::Interior)) - return find(cell.mData.mX, cell.mData.mY); - else - return find(cell.mName); - } + const ESM::Pathgrid *search(int x, int y) const; + const ESM::Pathgrid *search(const std::string& name) const; + const ESM::Pathgrid *find(int x, int y) const; + const ESM::Pathgrid* find(const std::string& name) const; + const ESM::Pathgrid *search(const ESM::Cell &cell) const; + const ESM::Pathgrid *find(const ESM::Cell &cell) const; }; - template - class IndexedStore - { - protected: - typedef typename std::map Static; - Static mStatic; + template <> + class Store : public IndexedStore + { public: - typedef typename std::map::const_iterator iterator; - - IndexedStore() {} - - iterator begin() const { - return mStatic.begin(); - } - - iterator end() const { - return mStatic.end(); - } - - void load(ESM::ESMReader &esm) { - T record; - record.load(esm); - - // Try to overwrite existing record - std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); - if (!ret.second) - ret.first->second = record; - } - - int getSize() const { - return mStatic.size(); - } - - void setUp() { - } - - const T *search(int index) const { - typename Static::const_iterator it = mStatic.find(index); - if (it != mStatic.end()) - return &(it->second); - return NULL; - } - - const T *find(int index) const { - const T *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << T::getRecordType() << " with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + Store(); }; template <> - struct Store : public IndexedStore + class Store : public IndexedStore { - Store() {} - }; - - template <> - struct Store : public IndexedStore - { - Store() {} + public: + Store(); }; template <> @@ -1058,52 +472,19 @@ namespace MWWorld public: typedef std::vector::const_iterator iterator; - Store() { - mStatic.reserve(ESM::Attribute::Length); - } + Store(); - const ESM::Attribute *search(size_t index) const { - if (index >= mStatic.size()) { - return 0; - } - return &mStatic.at(index); - } + const ESM::Attribute *search(size_t index) const; + const ESM::Attribute *find(size_t index) const; - const ESM::Attribute *find(size_t index) const { - const ESM::Attribute *ptr = search(index); - if (ptr == 0) { - std::ostringstream msg; - msg << "Attribute with index " << index << " not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } + void setUp(); - void setUp() { - for (int i = 0; i < ESM::Attribute::Length; ++i) { - mStatic.push_back( - ESM::Attribute( - ESM::Attribute::sAttributeIds[i], - ESM::Attribute::sGmstAttributeIds[i], - ESM::Attribute::sGmstAttributeDescIds[i] - ) - ); - } - } - - size_t getSize() const { - return mStatic.size(); - } - - iterator begin() const { - return mStatic.begin(); - } - - iterator end() const { - return mStatic.end(); - } + size_t getSize() const; + iterator begin() const; + iterator end() const; }; +#if 0 template<> inline void Store::setUp() { @@ -1122,6 +503,7 @@ namespace MWWorld mShared.push_back(&(it->second)); } } +#endif } //end namespace diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index eb7dd901d7..48c8be610a 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -243,11 +243,14 @@ namespace Gui std::string textureName = name; Ogre::Image image; image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA); - Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(textureName, - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, - width, height, 0, Ogre::PF_BYTE_RGBA); - texture->loadImage(image); + if (!Ogre::TextureManager::getSingleton().resourceExists(textureName)) + { + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(textureName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + width, height, 0, Ogre::PF_BYTE_RGBA); + texture->loadImage(image); + } if (exportToFile) image.save(resourceName + ".png"); diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index f54018a87f..90721cd3c9 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -73,7 +73,6 @@ public: static void lowerCaseInPlace(std::string &inout) { for (unsigned int i=0; i 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname)) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 0009c9f836..d6b261cd73 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -711,7 +711,7 @@ private: std::string fullname = name+"@index="+Ogre::StringConverter::toString(shape->recIndex); if(shape->name.length() > 0) fullname += "@shape="+shape->name; - Misc::StringUtils::toLower(fullname); + Misc::StringUtils::lowerCaseInPlace(fullname); Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton(); if(meshMgr.getByName(fullname).isNull()) @@ -944,7 +944,7 @@ private: std::string fullname = name+"@index="+Ogre::StringConverter::toString(partnode->recIndex); if(partnode->name.length() > 0) fullname += "@type="+partnode->name; - Misc::StringUtils::toLower(fullname); + Misc::StringUtils::lowerCaseInPlace(fullname); Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem(); @@ -1172,7 +1172,7 @@ private: nextpos = std::distance(str.begin(), ++last); } std::string result = str.substr(pos, nextpos-pos); - textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result))); + textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::lowerCase(result))); pos = nextpos; } @@ -1377,7 +1377,7 @@ ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string na { ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - Misc::StringUtils::toLower(name); + Misc::StringUtils::lowerCaseInPlace(name); NIFObjectLoader::load(parentNode, scene, name, group); for(size_t i = 0;i < scene->mEntities.size();i++) @@ -1399,7 +1399,7 @@ ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bo { ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - Misc::StringUtils::toLower(name); + Misc::StringUtils::lowerCaseInPlace(name); NIFObjectLoader::load(parentNode, scene, name, group); bool isskinned = false; @@ -1422,8 +1422,8 @@ ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bo // accepts anything named "filter*" or "tri filter*" std::string filter = "@shape=tri "+bonefilter; std::string filter2 = "@shape="+bonefilter; - Misc::StringUtils::toLower(filter); - Misc::StringUtils::toLower(filter2); + Misc::StringUtils::lowerCaseInPlace(filter); + Misc::StringUtils::lowerCaseInPlace(filter2); for(size_t i = 0;i < scene->mEntities.size();i++) { Ogre::Entity *entity = scene->mEntities[i]; @@ -1465,7 +1465,7 @@ ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string { ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); - Misc::StringUtils::toLower(name); + Misc::StringUtils::lowerCaseInPlace(name); NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000); if(scene->mSkelBase) diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 936bfb435f..e68221d1d7 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include /* FIXME: "Nif" isn't really an appropriate emitter name. */ class NifEmitter : public Ogre::ParticleEmitter @@ -173,7 +173,7 @@ public: Ogre::Real& timeToLive = particle->timeToLive; #endif - Ogre::Node* emitterBone = mEmitterBones.at(OEngine::Misc::Rng::rollDice(mEmitterBones.size())); + Ogre::Node* emitterBone = mEmitterBones.at(Misc::Rng::rollDice(mEmitterBones.size())); position = xOff + yOff + zOff + mParticleBone->_getDerivedOrientation().Inverse() * (emitterBone->_getDerivedPosition() diff --git a/libs/openengine/CMakeLists.txt b/libs/openengine/CMakeLists.txt index e9a1bcd9e2..7a0a791d11 100644 --- a/libs/openengine/CMakeLists.txt +++ b/libs/openengine/CMakeLists.txt @@ -23,7 +23,7 @@ set(OENGINE_BULLET bullet/trace.h ) -set(OENGINE_ALL ${OENGINE_GUI} ${OENGINE_BULLET}) +set(OENGINE_ALL ${OENGINE_OGRE} ${OENGINE_GUI} ${OENGINE_BULLET}) set(OENGINE_LIBRARY "oengine") set(OENGINE_LIBRARY ${OENGINE_LIBRARY} PARENT_SCOPE) diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index 741b672ff1..dbdc6c8b24 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -10,7 +10,7 @@ #include -#include +#include #include @@ -147,7 +147,7 @@ namespace Render void SelectionBuffer::getNextColour () { - Ogre::ARGB color = static_cast(OEngine::Misc::Rng::rollClosedProbability() * std::numeric_limits::max()); + Ogre::ARGB color = static_cast(Misc::Rng::rollClosedProbability() * std::numeric_limits::max()); if (mCurrentColour.getAsARGB () == color) { From 3982573035b726dd8a1ffbe261bc342f79c8aecb Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 6 Oct 2018 13:35:51 +1000 Subject: [PATCH 6/7] BSA enhancements. * Implement hash based lookup for TES3 BSA files. * Added TES4/TES5 BSA support. * Implemented a hack (non-portable code) in an attempt to reduce startup time under Windows because Boost::filesystem seems to take forever on GetFileAttributeW. This implementation uses FindFirstFile/FindNextFile/FindClose instead. --- CMakeLists.txt | 7 +- apps/opencs/CMakeLists.txt | 2 + apps/opencs/editor.cpp | 17 + apps/opencs/editor.hpp | 1 + apps/openmw/CMakeLists.txt | 2 + apps/openmw/engine.cpp | 6 + apps/openmw/engine.hpp | 2 + apps/openmw/main.cpp | 9 + components/CMakeLists.txt | 2 +- components/bsa/bsa_archive.cpp | 195 ++++++++++- components/bsa/bsa_archive.hpp | 1 + components/bsa/bsa_file.cpp | 69 +++- components/bsa/bsa_file.hpp | 2 + components/bsa/resources.cpp | 9 +- components/bsa/resources.hpp | 2 +- components/bsa/tes4bsa_file.cpp | 339 +++++++++++++++++++ components/bsa/tes4bsa_file.hpp | 103 ++++++ extern/BSAOpt/CMakeLists.txt | 17 + extern/BSAOpt/hash.cpp | 108 ++++++ extern/BSAOpt/hash.hpp | 42 +++ plugins/mygui_resource_plugin/CMakeLists.txt | 2 + 21 files changed, 917 insertions(+), 20 deletions(-) create mode 100644 components/bsa/tes4bsa_file.cpp create mode 100644 components/bsa/tes4bsa_file.hpp create mode 100644 extern/BSAOpt/CMakeLists.txt create mode 100644 extern/BSAOpt/hash.cpp create mode 100644 extern/BSAOpt/hash.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ff5191c462..2ec616cbbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,9 @@ if(USE_SYSTEM_TINYXML) endif() endif() +# TES4 +find_package(ZLIB REQUIRED) + # Platform specific if (WIN32) if(NOT MINGW) @@ -257,9 +260,10 @@ include_directories("." ${LIBS_DIR} ${MYGUI_PLATFORM_INCLUDE_DIRS} ${OPENAL_INCLUDE_DIR} ${BULLET_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} ) -link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MYGUI_LIB_DIR}) +link_directories(${SDL2_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS} ${OGRE_LIB_DIR} ${MYGUI_LIB_DIR} ${ZLIB_LIB_DIR}) if(MYGUI_STATIC) add_definitions(-DMYGUI_STATIC) @@ -577,6 +581,7 @@ add_subdirectory (extern/ogre-ffmpeg-videoplayer) add_subdirectory (extern/oics) add_subdirectory (extern/sdl4ogre) add_subdirectory (extern/murmurhash) +add_subdirectory (extern/BSAOpt) # Components add_subdirectory (components) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7f3709b8a9..ec686c4e28 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -211,7 +211,9 @@ target_link_libraries(openmw-cs ${OGRE_Overlay_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${ZLIB_LIBRARY} ${MURMURHASH_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 79f32619e1..cd5f6753d1 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -30,6 +30,7 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { std::pair > config = readConfig(); + std::vector tes4config = readTES4Config(); setupDataFiles (config.first); @@ -44,6 +45,9 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) Bsa::registerResources (Files::Collections (config.first, !mFsStrict), config.second, true, mFsStrict); + // useLooseFiles is set false, since it is already done above + Bsa::registerResources (Files::Collections (config.first, !mFsStrict), tes4config, /*useLooseFiles*/false, + mFsStrict, /*isTes4*/true); mDocumentManager.listResources(); @@ -182,6 +186,19 @@ std::pair > CS::Editor::readConfi return std::make_pair (canonicalPaths, variables["fallback-archive"].as >()); } +std::vector CS::Editor::readTES4Config() +{ + boost::program_options::variables_map variables; + boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); + + desc.add_options() + ("fallback-tes4archive", boost::program_options::value >()-> + default_value(std::vector(), "fallback-tes4archive")->multitoken()); + + mCfgMgr.readConfiguration(variables, desc, /*quiet*/true); + return variables["fallback-tes4archive"].as >(); +} + void CS::Editor::createGame() { mStartup.hide(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 33f5fd3b32..5eae1258ad 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -73,6 +73,7 @@ namespace CS void setupDataFiles (const Files::PathContainer& dataDirs); std::pair > readConfig(bool quiet=false); + std::vector readTES4Config(); ///< \return data paths // not implemented diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index ffa52042ca..cdb1691f00 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -117,6 +117,8 @@ target_link_libraries(openmw ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} ${SHINY_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} + ${ZLIB_LIBRARY} ${OPENAL_LIBRARY} ${SOUND_INPUT_LIBRARY} ${BULLET_LIBRARIES} diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 539539db94..1bc3b48c5c 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -252,6 +252,10 @@ void OMW::Engine::addArchive (const std::string& archive) { mArchives.push_back(archive); } +void OMW::Engine::addTES4Archive (const std::string& archive) { + mTES4Archives.push_back(archive); +} + // Set resource dir void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir) { @@ -365,6 +369,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mOgre->createWindow("OpenMW", windowSettings); Bsa::registerResources (mFileCollections, mArchives, true, mFSStrict); + // useLooseFiles is set false, since it is already done above + Bsa::registerResources (mFileCollections, mTES4Archives, /*useLooseFiles*/false, mFSStrict, /*isTes4*/true); // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 3b088595c0..79b047cd1c 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -67,6 +67,7 @@ namespace OMW ToUTF8::Utf8Encoder* mEncoder; Files::PathContainer mDataDirs; std::vector mArchives; + std::vector mTES4Archives; boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; @@ -137,6 +138,7 @@ namespace OMW /// Add BSA archive void addArchive(const std::string& archive); + void addTES4Archive(const std::string& archive); /// Set resource dir void setResourceDir(const boost::filesystem::path& parResDir); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 2d2c9af0c9..c36fcc5e98 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -112,6 +112,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("fallback-archive", bpo::value()->default_value(StringsVector(), "fallback-archive") ->multitoken(), "set fallback BSA archives (later archives have higher priority)") + ("fallback-tes4archive", bpo::value()->default_value(StringsVector(), "fallback-tes4archive") + ->multitoken(), "set fallback TES4 BSA archives (later archives have higher priority)") + ("resources", bpo::value()->default_value("resources"), "set resources directory") @@ -240,6 +243,12 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.addArchive(*it); } + StringsVector tes4archives = variables["fallback-tes4archive"].as(); + for (StringsVector::const_iterator it = tes4archives.begin(); it != tes4archives.end(); ++it) + { + engine.addTES4Archive(*it); + } + engine.setResourceDir(variables["resources"].as()); StringsVector content = variables["content"].as(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index da0428ec25..9b6f9fef92 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -31,7 +31,7 @@ add_component_dir (nifoverrides ) add_component_dir (bsa - bsa_archive bsa_file resources + bsa_archive bsa_file resources tes4bsa_file ) add_component_dir (nif diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 12836ea70b..114abee207 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -23,7 +23,10 @@ #include "bsa_archive.hpp" +#include + #include +#include #include #include @@ -41,10 +44,92 @@ #define OGRE_CONST #endif +#include #include "bsa_file.hpp" +#include "tes4bsa_file.hpp" #include "../files/constrainedfiledatastream.hpp" +namespace +{ +// Concepts from answer by Remy Lebeau +// https://stackoverflow.com/questions/15068475/recursive-hard-disk-search-with-findfirstfile-findnextfile-c +// +// Also see https://msdn.microsoft.com/en-us/library/aa365200%28VS.85%29.aspx +// +// From 34.5 sec down to 18.5 sec on laptop with many of the data files on an external USB drive +#if defined _WIN32 || defined _WIN64 + +#include +#include // auto_ptr + + // FIXME: not tested unicode path and filenames + DWORD indexFiles(const std::string& rootDir, const std::string& subdir, + std::map& files, std::map& index) + { + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd; + std::string path = rootDir + ((subdir == "") ? "" : "\\" +subdir); + + hFind = FindFirstFile((path + "\\*").c_str(), &ffd); + if (INVALID_HANDLE_VALUE == hFind) + return ERROR_INVALID_HANDLE; + + std::auto_ptr > subDirs; + std::string filename; + + do + { + filename = std::string(ffd.cFileName); + + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (filename != "." && filename != "..") + { + if (subDirs.get() == nullptr) + subDirs.reset(new std::vector); + + subDirs->push_back(filename); + } + } + else + { + std::uint64_t folderHash = GenOBHash(subdir, filename); + + std::map::iterator iter = files.find(folderHash); + if (iter != files.end()) + throw std::runtime_error ("duplicate hash found"); + + files[folderHash] = path + "\\" + filename; + + std::string entry = ((subdir == "") ? "" : subdir + "\\") + filename; + std::replace(entry.begin(), entry.end(), '\\', '/'); + index.insert(std::make_pair (entry, path + "/" + filename)); + } + } while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); + + DWORD dwError = GetLastError(); + if (dwError != ERROR_NO_MORE_FILES) + return dwError; + + if (subDirs.get() != nullptr) + { + for (size_t i = 0; i < subDirs->size(); ++i) + { + std::string dir = subDirs->at(i); + boost::algorithm::to_lower(dir); + // FIXME: ignoring errors for now + dwError = indexFiles(rootDir, ((subdir == "") ? "" : subdir + "\\") + dir, files, index); + } + } + + return 0; + } +#endif +} + using namespace Ogre; static bool fsstrict = false; @@ -78,6 +163,8 @@ class DirArchive: public Ogre::Archive index mIndex; + std::map mFiles; + index::const_iterator lookup_filename (std::string const & filename) const { std::string normalized = normalize_path (filename.begin (), filename.end ()); @@ -89,6 +176,10 @@ public: DirArchive(const String& name) : Archive(name, "Dir") { +#if defined _WIN32 || defined _WIN64 + indexFiles(name, "", mFiles, mIndex); +#else + typedef boost::filesystem::recursive_directory_iterator directory_iterator; directory_iterator end; @@ -109,6 +200,7 @@ public: mIndex.insert (std::make_pair (searchable, proper)); } +#endif } bool isCaseSensitive() const { return fsstrict; } @@ -119,6 +211,27 @@ public: virtual DataStreamPtr open(const String& filename, bool readonly = true) OGRE_CONST { +#if defined _WIN32 || defined _WIN64 + boost::filesystem::path p(filename); + std::string file = p.filename().string(); + + p.remove_filename(); + std::string dir = p.string(); + boost::algorithm::to_lower(dir); + std::replace(dir.begin(), dir.end(), '/', '\\'); + + std::uint64_t hash = GenOBHash(dir, file); + + std::map::const_iterator it = mFiles.find(hash); + if (it == mFiles.end()) + { + std::ostringstream os; + os << "The file '" << filename << "' could not be found."; + throw std::runtime_error (os.str()); + } + + return openConstrainedFileDataStream (it->second.c_str()); +#else index::const_iterator i = lookup_filename (filename); if (i == mIndex.end ()) @@ -129,6 +242,7 @@ public: } return openConstrainedFileDataStream (i->second.c_str ()); +#endif } StringVectorPtr list(bool recursive = true, bool dirs = false) @@ -157,7 +271,25 @@ public: bool exists(const String& filename) { +#if defined _WIN32 || defined _WIN64 + boost::filesystem::path p(filename); + std::string file = p.filename().string(); + + p.remove_filename(); + std::string dir = p.string(); + boost::algorithm::to_lower(dir); + std::replace(dir.begin(), dir.end(), '/', '\\'); + + std::uint64_t hash = GenOBHash(dir, file); + + std::map::const_iterator it = mFiles.find(hash); + if (it == mFiles.end()) + return false; + + return true; +#else return lookup_filename(filename) != mIndex.end (); +#endif } time_t getModifiedTime(const String&) { return 0; } @@ -223,6 +355,8 @@ public: : Archive(name, "BSA") { arc.open(name); } + BSAArchive(const String& name, const std::string& type) : Archive(name, type) {} + bool isCaseSensitive() const { return false; } // The archive is loaded in the constructor, and never unloaded. @@ -241,7 +375,7 @@ public: return narc->getFile(filename.c_str()); } - bool exists(const String& filename) { + virtual bool exists(const String& filename) { return arc.exists(filename.c_str()); } @@ -259,7 +393,7 @@ public: return findFileInfo ("*", recursive, dirs); } - StringVectorPtr find(const String& pattern, bool recursive = true, + virtual StringVectorPtr find(const String& pattern, bool recursive = true, bool dirs = false) { std::string normalizedPattern = normalize_path(pattern.begin(), pattern.end()); @@ -306,6 +440,24 @@ public: } }; +class TES4BSAArchive : public BSAArchive +{ + Bsa::TES4BSAFile arc; + +public: + TES4BSAArchive::TES4BSAArchive(const String& name) : BSAArchive(name, "TES4BSA") { arc.open(name); } + + virtual DataStreamPtr open(const String& filename, bool readonly = true) + { + return arc.getFile(filename); + } + + virtual bool exists(const String& filename) + { + return arc.exists(filename); + } +}; + // An archive factory for BSA archives class BSAArchiveFactory : public ArchiveFactory { @@ -351,9 +503,32 @@ public: void destroyInstance( Archive* arch) { delete arch; } }; +class TES4BSAArchiveFactory : public ArchiveFactory +{ +public: + const String& getType() const + { + static String name = "TES4BSA"; + return name; + } + + Archive *createInstance( const String& name ) + { + return new TES4BSAArchive(name); + } + + virtual Archive* createInstance(const String& name, bool readOnly) + { + return new TES4BSAArchive(name); + } + + void destroyInstance( Archive* arch) { delete arch; } +}; + static bool init = false; static bool init2 = false; +static bool init3 = false; static void insertBSAFactory() { @@ -364,6 +539,15 @@ static void insertBSAFactory() } } +static void insertTES4BSAFactory() +{ + if(!init3) + { + ArchiveManager::getSingleton().addArchiveFactory( new TES4BSAArchiveFactory ); + init3 = true; + } +} + static void insertDirFactory() { if(!init2) @@ -386,6 +570,13 @@ void addBSA(const std::string& name, const std::string& group) addResourceLocation(name, "BSA", group, true); } +void addTES4BSA(const std::string& name, const std::string& group) +{ + insertTES4BSAFactory(); + ResourceGroupManager::getSingleton(). + addResourceLocation(name, "TES4BSA", group, true); +} + void addDir(const std::string& name, const bool& fs, const std::string& group) { fsstrict = fs; diff --git a/components/bsa/bsa_archive.hpp b/components/bsa/bsa_archive.hpp index 7f9ebaae10..d930c2571d 100644 --- a/components/bsa/bsa_archive.hpp +++ b/components/bsa/bsa_archive.hpp @@ -33,6 +33,7 @@ namespace Bsa /// Add the given BSA file as an input archive in the Ogre resource /// system. void addBSA(const std::string& file, const std::string& group="General"); +void addTES4BSA(const std::string& file, const std::string& group="General"); void addDir(const std::string& file, const bool& fs, const std::string& group="General"); } diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 3bf73ede27..40bfce2ac0 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -27,9 +27,41 @@ #include #include +#include #include "../files/constrainedfiledatastream.hpp" +namespace +{ + // see: http://en.uesp.net/wiki/Tes3Mod:BSA_File_Format + std::uint64_t getHash(const char *name) + { + unsigned int len = (unsigned int)strlen(name); + std::uint64_t hash; + + unsigned l = (len>>1); + unsigned sum, off, temp, i, n, hash1; + + for(sum = off = i = 0; i < l; i++) { + sum ^= (((unsigned)(name[i]))<<(off&0x1F)); + off += 8; + } + hash1 = sum; + + for(sum = off = 0; i < len; i++) { + temp = (((unsigned)(name[i]))<<(off&0x1F)); + sum ^= temp; + n = temp & 0x1F; + sum = (sum << (32-n)) | (sum >> n); // binary "rotate right" + off += 8; + } + hash = sum; + hash <<= 32; + hash += hash1; + return hash; + } +} + using namespace std; using namespace Bsa; @@ -143,7 +175,14 @@ void BSAFile::readHeader() fail("Archive contains offsets outside itself"); // Add the file name to the lookup - lookup[fs.name] = i; + //lookup[fs.name] = i; + } + + std::uint64_t hash; + for (size_t i = 0; i < filenum; ++i) + { + input.read(reinterpret_cast(&hash), 8); + mFiles[hash] = files[i]; } isLoaded = true; @@ -152,13 +191,14 @@ void BSAFile::readHeader() /// Get the index of a given file name, or -1 if not found int BSAFile::getIndex(const char *str) const { - Lookup::const_iterator it = lookup.find(str); - if(it == lookup.end()) + std::string name(str); + boost::algorithm::to_lower(name); + std::uint64_t hash = getHash(name.c_str()); + std::map::const_iterator iter = mFiles.find(hash); + if (iter != mFiles.end()) + return 0; // NOTE: this is a bit of a hack, exists() only checks for '-1' + else return -1; - - int res = it->second; - assert(res >= 0 && (size_t)res < files.size()); - return res; } /// Open an archive file. @@ -171,10 +211,15 @@ void BSAFile::open(const string &file) Ogre::DataStreamPtr BSAFile::getFile(const char *file) { assert(file); - int i = getIndex(file); - if(i == -1) - fail("File not found: " + string(file)); - const FileStruct &fs = files[i]; - return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize); + std::string name(file); + boost::algorithm::to_lower(name); + std::uint64_t hash = getHash(name.c_str()); + std::map::const_iterator it = mFiles.find(hash); + if (it != mFiles.end()) + { + const FileStruct &fs = it->second; + return openConstrainedFileDataStream (filename.c_str (), fs.offset, fs.fileSize); + } + fail("File not found: " + string(file)); } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 017adf1e31..5379d20e3c 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -82,6 +82,8 @@ private: typedef std::map Lookup; Lookup lookup; + std::map mFiles; + /// Error handling void fail(const std::string &msg); diff --git a/components/bsa/resources.cpp b/components/bsa/resources.cpp index b66da1a766..c5075ec69a 100644 --- a/components/bsa/resources.cpp +++ b/components/bsa/resources.cpp @@ -9,7 +9,7 @@ #include "bsa_archive.hpp" void Bsa::registerResources (const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles, bool fsStrict) + const std::vector& archives, bool useLooseFiles, bool fsStrict, bool isTes4) { const Files::PathContainer& dataDirs = collections.getPaths(); @@ -34,13 +34,16 @@ void Bsa::registerResources (const Files::Collections& collections, if (collections.doesExist(*archive)) { // Last BSA has the highest priority - std::string groupName = "DataBSA" + Ogre::StringConverter::toString(archives.size()-i, 8, '0'); + std::string groupName = (isTes4 ? "TES4BSA" : "DataBSA") + Ogre::StringConverter::toString(archives.size()-i, 8, '0'); Ogre::ResourceGroupManager::getSingleton ().createResourceGroup (groupName); const std::string archivePath = collections.getPath(*archive).string(); std::cout << "Adding BSA archive " << archivePath << std::endl; - Bsa::addBSA(archivePath, groupName); + if (!isTes4) + Bsa::addBSA(archivePath, groupName); + else + Bsa::addTES4BSA(archivePath, groupName); ++i; } else diff --git a/components/bsa/resources.hpp b/components/bsa/resources.hpp index 8c3fb7bef8..b0723b3eb2 100644 --- a/components/bsa/resources.hpp +++ b/components/bsa/resources.hpp @@ -9,7 +9,7 @@ namespace Bsa { void registerResources (const Files::Collections& collections, - const std::vector& archives, bool useLooseFiles, bool fsStrict); + const std::vector& archives, bool useLooseFiles, bool fsStrict, bool isTes4=false); ///< Register resources directories and archives as OGRE resources groups } diff --git a/components/bsa/tes4bsa_file.cpp b/components/bsa/tes4bsa_file.cpp new file mode 100644 index 0000000000..33caefd0b3 --- /dev/null +++ b/components/bsa/tes4bsa_file.cpp @@ -0,0 +1,339 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (bsa_file.cpp) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff added by cc9cii 2018 + + */ +#include "tes4bsa_file.hpp" + +#include +#include + +#include +#include +#include +#include + +#include + +#include // see: http://en.uesp.net/wiki/Tes4Mod:Hash_Calculation + +#undef TEST_UNIQUE_HASH + +namespace +{ + void getBZString(std::string& str, boost::filesystem::ifstream& filestream) + { + char size = 0; + filestream.read(&size, 1); + + boost::scoped_array buf(new char[size]); + filestream.read(buf.get(), size); + + if (buf[size - 1] != 0) + str.assign(buf.get(), size); + else + str.assign(buf.get(), size - 1); // don't copy null terminator + assert((size_t)size-1 == str.size() && "getBZString string size mismatch"); + return; + } +} + +using namespace Bsa; + + +/// Error handling +void TES4BSAFile::fail(const std::string& msg) +{ + throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename); +} + +/// Read header information from the input source +void TES4BSAFile::readHeader() +{ + assert(!isLoaded); + + namespace bfs = boost::filesystem; + bfs::ifstream input(bfs::path(mFilename), std::ios_base::binary); + + // Total archive size + std::streamoff fsize = 0; + if(input.seekg(0, std::ios_base::end)) + { + fsize = input.tellg(); + input.seekg(0); + } + + if(fsize < 36) // header is 36 bytes + fail("File too small to be a valid BSA archive"); + + // Get essential header numbers + //size_t dirsize, filenum; + std::uint32_t archiveFlags, folderCount, fileCount, totalFileNameLength; + { + // First 36 bytes + std::uint32_t header[9]; + + input.read(reinterpret_cast(header), 36); + + if(header[0] != 0x00415342 /*"BSA\x00"*/ || (header[1] != 0x67 /*TES4*/ && header[1] != 0x68 /*TES5*/)) + fail("Unrecognized TES4 BSA header"); + + // header[2] is offset, should be 36 = 0x24 which is the size of the header + + // Oblivion - Meshes.bsa + // + // 0111 1000 0111 = 0x0787 + // ^^^ ^ ^^^ + // ||| | ||+-- has names for dirs (mandatory?) + // ||| | |+--- has names for files (mandatory?) + // ||| | +---- files are compressed by default + // ||| | + // ||| +---------- unknown (TES5: retain strings during startup) + // ||+------------ unknown (TES5: embedded file names) + // |+------------- unknown + // +-------------- unknown + // + archiveFlags = header[3]; + folderCount = header[4]; + fileCount = header[5]; + //totalFolderNameLength = header[6]; + totalFileNameLength = header[7]; + //fileFlags = header[8]; // FIXME: an opportunity to optimize here + + mCompressedByDefault = (archiveFlags & 0x4) != 0; + mEmbeddedFileNames = header[1] == 0x68 /*TES5*/ && (archiveFlags & 0x100) != 0; + } + + // TODO: more checks for BSA file corruption + + // folder records + std::uint64_t hash; + FolderRecord fr; + for (std::uint32_t i = 0; i < folderCount; ++i) + { + input.read(reinterpret_cast(&hash), 8); + input.read(reinterpret_cast(&fr.count), 4); // not sure purpose of count + input.read(reinterpret_cast(&fr.offset), 4); // not sure purpose of offset + + std::map::const_iterator lb = mFolders.lower_bound(hash); + if (lb != mFolders.end() && !(mFolders.key_comp()(hash, lb->first))) + fail("Archive found duplicate folder name hash"); + else + mFolders.insert(lb, std::pair(hash, fr)); + } + + // file record blocks + std::uint64_t fileHash; + FileRecord file; + + std::string folder(""); + std::uint64_t folderHash; + if ((archiveFlags & 0x1) == 0) + folderCount = 1; // TODO: not tested + + for (std::uint32_t i = 0; i < folderCount; ++i) + { + if ((archiveFlags & 0x1) != 0) + getBZString(folder, input); + + folderHash = GenOBHash(folder, std::string("")); + + std::map::iterator iter = mFolders.find(folderHash); + if (iter == mFolders.end()) + fail("Archive folder name hash not found"); + + for (std::uint32_t j = 0; j < iter->second.count; ++j) + { + input.read(reinterpret_cast(&fileHash), 8); + input.read(reinterpret_cast(&file.size), 4); + input.read(reinterpret_cast(&file.offset), 4); + + std::map::const_iterator lb = iter->second.files.lower_bound(fileHash); + if (lb != iter->second.files.end() && !(iter->second.files.key_comp()(fileHash, lb->first))) + fail("Archive found duplicate file name hash"); + + iter->second.files.insert(lb, std::pair(fileHash, file)); + } + } + + // file record blocks + if ((archiveFlags & 0x2) != 0) + { + mStringBuf.resize(totalFileNameLength); + input.read(&mStringBuf[0], mStringBuf.size()); // TODO: maybe useful in building a lookup map? + } + + // TODO: more checks for BSA file corruption + + isLoaded = true; +} + +TES4BSAFile::FileRecord TES4BSAFile::getFileRecord(const std::string& str) const +{ + boost::filesystem::path p(str); + std::string stem = p.stem().string(); + std::string ext = p.extension().string(); + std::string filename = p.filename().string(); + p.remove_filename(); + + std::string folder = p.string(); + // GenOBHash already converts to lowercase and replaces file separators but not for path + boost::algorithm::to_lower(folder); + std::replace(folder.begin(), folder.end(), '/', '\\'); + + std::uint64_t folderHash = GenOBHash(folder, std::string("")); + + std::map::const_iterator it = mFolders.find(folderHash); + if (it == mFolders.end()) + return FileRecord(); // folder not found, return default which has offset of -1 + + boost::algorithm::to_lower(stem); + boost::algorithm::to_lower(ext); + std::uint64_t fileHash = GenOBHashPair(stem, ext); + std::map::const_iterator iter = it->second.files.find(fileHash); + if (iter == it->second.files.end()) + return FileRecord(); // file not found, return default which has offset of -1 + + // cache for next time + std::uint64_t hash = GenOBHash(folder, filename); + +#if defined (TEST_UNIQUE_HASH) + FileList::const_iterator lb = mFiles.lower_bound(hash); + if (lb != mFiles.end() && !(mFiles.key_comp()(hash, lb->first))) + { + // found, check if same filename + if (lb->second.fileName == str) + return iter->second; // same name, should not have got here!! + else + { + // different filename, hash is not unique! + std::cerr << "BSA hash collision: " << str << std::hex << "0x" << hash << std::endl; + + return iter->second; // return without cashing + } + } + + // not found, cache for later + const_cast(mFiles).insert(lb, std::pair(hash, iter->second)); + const_cast(mFiles)[hash].fileName = str; +#else + const_cast(mFiles)[hash] = iter->second; // NOTE: const hack +#endif + return iter->second; +} + +bool TES4BSAFile::exists(const std::string& str) const +{ + // check cache first + boost::filesystem::path p(str); + std::string filename = p.filename().string(); + p.remove_filename(); + + std::string folder = p.string(); + // GenOBHash already converts to lowercase and replaces file separators but not for path + boost::algorithm::to_lower(folder); + std::replace(folder.begin(), folder.end(), '/', '\\'); + + std::uint64_t hash = GenOBHash(folder, filename); + + std::map::const_iterator it = mFiles.find(hash); +#if defined (TEST_UNIQUE_HASH) + if (it != mFiles.end() && it->second.fileName == str) +#else + if (it != mFiles.end()) +#endif + return true; + else + return getFileRecord(str).offset != -1; +} + +void TES4BSAFile::open(const std::string& file) +{ + mFilename = file; + readHeader(); +} + +Ogre::DataStreamPtr TES4BSAFile::getFile(const std::string& file) +{ + assert(file); + + FileRecord fileRec = getFileRecord(file); + if(fileRec.offset == -1) + fail("File not found: " + std::string(file)); + + boost::filesystem::ifstream input(boost::filesystem::path(mFilename), std::ios_base::binary); + input.seekg(fileRec.offset); + + std::string fullPath; + if (mEmbeddedFileNames) + getBZString(fullPath, input); // TODO: maybe cache the hash and/or offset of frequently used ones? + + if (( mCompressedByDefault && (fileRec.size & (1<<30)) == 0) + || + (!mCompressedByDefault && (fileRec.size & (1<<30)) != 0)) + { + std::uint32_t bufSize = 0; + boost::scoped_array inBuf; + inBuf.reset(new unsigned char[fileRec.size-4]); + input.read(reinterpret_cast(&bufSize), 4); + input.read(reinterpret_cast(inBuf.get()), fileRec.size-4); + Ogre::MemoryDataStream *outBuf = new Ogre::MemoryDataStream(bufSize); + Ogre::SharedPtr streamPtr(outBuf); + + int ret; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = bufSize; + strm.next_in = inBuf.get(); + ret = inflateInit(&strm); + if (ret != Z_OK) + throw std::runtime_error("TES4BSAFile::getFile - inflateInit failed"); + + strm.avail_out = bufSize; + strm.next_out = outBuf->getPtr(); + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR && "TES4BSAFile::getFile - inflate - state clobbered"); + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + throw std::runtime_error("TES4BSAFile::getFile - inflate failed"); + } + assert(ret == Z_OK || ret == Z_STREAM_END); + inflateEnd(&strm); + + return streamPtr; + } + else // not compressed TODO: not tested + { + Ogre::MemoryDataStream *outBuf = new Ogre::MemoryDataStream(fileRec.size); + Ogre::SharedPtr streamPtr(outBuf); + input.read(reinterpret_cast(outBuf->getPtr()), fileRec.size); + + return streamPtr; + } +} diff --git a/components/bsa/tes4bsa_file.hpp b/components/bsa/tes4bsa_file.hpp new file mode 100644 index 0000000000..979a6af1a0 --- /dev/null +++ b/components/bsa/tes4bsa_file.hpp @@ -0,0 +1,103 @@ +/* + OpenMW - The completely unofficial reimplementation of Morrowind + Copyright (C) 2008-2010 Nicolay Korslund + Email: < korslund@gmail.com > + WWW: http://openmw.sourceforge.net/ + + This file (bsa_file.h) is part of the OpenMW package. + + OpenMW is distributed as free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License + version 3, as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + version 3 along with this program. If not, see + http://www.gnu.org/licenses/ . + + TES4 stuff added by cc9cii 2018 + + */ + +#ifndef BSA_TES4BSA_FILE_H +#define BSA_TES4BSA_FILE_H + +#include +#include +#include +#include + +#include + + +namespace Bsa +{ + class TES4BSAFile + { + public: + struct FileRecord + { + std::uint32_t size; + std::uint32_t offset; + + std::string fileName; // NOTE: for testing hash collision only, see TEST_UNIQUE_HASH + + FileRecord() : size(0), offset(-1) {} + }; + + private: + /// Filenames string buffer + std::vector mStringBuf; + + /// True when an archive has been loaded + bool isLoaded; + + bool mCompressedByDefault; + bool mEmbeddedFileNames; + + std::map mFiles; + typedef std::map FileList; + + struct FolderRecord + { + std::uint32_t count; + std::uint32_t offset; + std::map files; + }; + std::map mFolders; + + FileRecord getFileRecord(const std::string& str) const; + + /// Used for error messages and getting files + std::string mFilename; + + /// Error handling + void fail(const std::string &msg); + + /// Read header information from the input source + void readHeader(); + + public: + TES4BSAFile() + : isLoaded(false), mCompressedByDefault(false), mEmbeddedFileNames(false) + { } + + /// Open an archive file. + void open(const std::string &file); + + /// Check if a file exists + bool exists(const std::string& file) const; + + Ogre::DataStreamPtr getFile(const std::string& file); + + /// Get a list of all files + const FileList &getList() const // FIXME + { return mFiles; } + }; +} + +#endif diff --git a/extern/BSAOpt/CMakeLists.txt b/extern/BSAOpt/CMakeLists.txt new file mode 100644 index 0000000000..a7f1dcf74a --- /dev/null +++ b/extern/BSAOpt/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8) + +# This is NOT intended as a stand-alone build system! Instead, you should include this from the main CMakeLists of your project. + +set(BSAOPTHASH_LIBRARY "bsaopthash") + +# Sources +set(SOURCE_FILES + hash.cpp +) + +add_library(${BSAOPTHASH_LIBRARY} STATIC ${SOURCE_FILES}) + +set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARY}) + +link_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(BSAOPTHASH_LIBRARIES ${BSAOPTHASH_LIBRARIES} PARENT_SCOPE) diff --git a/extern/BSAOpt/hash.cpp b/extern/BSAOpt/hash.cpp new file mode 100644 index 0000000000..029c542799 --- /dev/null +++ b/extern/BSAOpt/hash.cpp @@ -0,0 +1,108 @@ +/* Version: MPL 1.1/LGPL 3.0 + * + * "The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is BSAopt. + * + * The Initial Developer of the Original Code is + * Ethatron . Portions created by The Initial + * Developer are Copyright (C) 2011 The Initial Developer. + * All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Library General Public License Version 3 license (the + * "LGPL License"), in which case the provisions of LGPL License are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of the LGPL License and not + * to allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL License. + * If you do not delete the provisions above, a recipient may use your + * version of this file under either the MPL or the LGPL License." + */ + +#include +#include + +#include "hash.hpp" + +std::uint32_t GenOBHashStr(const std::string& s) { + std::uint32_t hash = 0; + + for (std::size_t i = 0; i < s.length(); i++) { + hash *= 0x1003F; + hash += (unsigned char)s[i]; + } + + return hash; +} + +std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext) { + std::uint64_t hash = 0; + + if (fle.length() > 0) { + hash = (std::uint64_t)( + (((unsigned char)fle[fle.length() - 1]) * 0x1) + + ((fle.length() > 2 ? (unsigned char)fle[fle.length() - 2] : (unsigned char)0) * 0x100) + + (fle.length() * 0x10000) + + (((unsigned char)fle[0]) * 0x1000000) + ); + + if (fle.length() > 3) { + hash += (std::uint64_t)(GenOBHashStr(fle.substr(1, fle.length() - 3)) * 0x100000000); + } + } + + if (ext.length() > 0) { + hash += (std::uint64_t)(GenOBHashStr(ext) * 0x100000000LL); + + unsigned char i = 0; + if (ext == ".nif") i = 1; + if (ext == ".kf" ) i = 2; + if (ext == ".dds") i = 3; + if (ext == ".wav") i = 4; + + if (i != 0) { + unsigned char a = (unsigned char)(((i & 0xfc ) << 5) + (unsigned char)((hash & 0xff000000) >> 24)); + unsigned char b = (unsigned char)(((i & 0xfe ) << 6) + (unsigned char)( hash & 0x000000ff) ); + unsigned char c = (unsigned char)(( i << 7) + (unsigned char)((hash & 0x0000ff00) >> 8)); + + hash -= hash & 0xFF00FFFF; + hash += (std::uint32_t)((a << 24) + b + (c << 8)); + } + } + + return hash; +} + +std::uint64_t GenOBHash(const std::string& path, std::string& file) { + std::transform(file.begin(), file.end(), file.begin(), ::tolower); + std::replace(file.begin(), file.end(), '/', '\\'); + + std::string fle; + std::string ext; + + const char *_fle = file.data(); + const char *_ext = strrchr(_fle, '.'); + if (_ext) { + ext = file.substr((0 + _ext) - _fle); + fle = file.substr(0, ( _ext) - _fle); + } + else { + ext = ""; + fle = file; + } + + if (path.length() && fle.length()) + return GenOBHashPair(path + "\\" + fle, ext); + else + return GenOBHashPair(path + fle, ext); +} diff --git a/extern/BSAOpt/hash.hpp b/extern/BSAOpt/hash.hpp new file mode 100644 index 0000000000..cd936530a8 --- /dev/null +++ b/extern/BSAOpt/hash.hpp @@ -0,0 +1,42 @@ +/* Version: MPL 1.1/LGPL 3.0 + * + * "The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is BSAopt. + * + * The Initial Developer of the Original Code is + * Ethatron . Portions created by The Initial + * Developer are Copyright (C) 2011 The Initial Developer. + * All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Library General Public License Version 3 license (the + * "LGPL License"), in which case the provisions of LGPL License are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of the LGPL License and not + * to allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL License. + * If you do not delete the provisions above, a recipient may use your + * version of this file under either the MPL or the LGPL License." + */ +#ifndef BSAOPT_HASH_H +#define BSAOPT_HASH_H + +#include + +std::uint32_t GenOBHashStr(const std::string& s); + +std::uint64_t GenOBHashPair(const std::string& fle, const std::string& ext); + +std::uint64_t GenOBHash(const std::string& path, std::string& file); + +#endif // BSAOPT_HASH_H diff --git a/plugins/mygui_resource_plugin/CMakeLists.txt b/plugins/mygui_resource_plugin/CMakeLists.txt index 72965c9178..6ffa5bc905 100644 --- a/plugins/mygui_resource_plugin/CMakeLists.txt +++ b/plugins/mygui_resource_plugin/CMakeLists.txt @@ -28,5 +28,7 @@ set_target_properties(${MYGUI_RESOURCE_PLUGIN_LIBRARY} PROPERTIES PREFIX "") target_link_libraries(${MYGUI_RESOURCE_PLUGIN_LIBRARY} ${OGRE_LIBRARIES} ${MYGUI_LIBRARIES} + ${BSAOPTHASH_LIBRARIES} # components/bsa + ${ZLIB_LIBRARIES} # components/bsa components ) From 5b65b2171a28470d7379b29e3fb6b1be05579c52 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 6 Oct 2018 18:39:06 +1000 Subject: [PATCH 7/7] README update. --- README.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 95380e39b5..32fe426de2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Font Licenses: Wrong Way, Go Back ------------------ -This is a fork of an old version of OpenMW. This version is probably not what you are looking for. It is still stuck on Ogre 1.9 and hence does not have any of the recent graphics enhancements. I suggest you use the [official release](https://github.com/OpenMW/openmw) instead. +This is a fork of an old version of OpenMW. This version is probably not what you are looking for. It is still stuck on Ogre 1.10 and hence does not have any of the recent graphics enhancements. I suggest you use the [official release](https://github.com/OpenMW/openmw) instead. Getting Started --------------- @@ -106,11 +106,9 @@ Command line options Changes ------- -Some of the differences with the official release are listed below. They are almost all to do with OpenCS. +Some of the differences with the official release are listed below. Initial changes are mostly to do with OpenCS: * Various minor bug fixes. -* Experimental support of loading TES4/TES5 records (coming soon). -* Experimental support of navMesh (coming soon). * C++11 features are used (or at least those available on MSVC 2013 onwards). * Loading time improvements. * Loading progress bar changes. @@ -125,3 +123,21 @@ Some of the differences with the official release are listed below. They are al * User preferences setting to workaround some X window managers not keeping pre-maximised state. * Use opencs.ini to store window states between sessions. +Enhancements for both OpenMW and OpenCS: + +* Hash based lookup for TES3 BSA files. +* TES4/TES5 BSA support. +* Experimental support of loading TES4/TES5 records (coming soon). +* Experimental support of NavMesh (eventually). + +Build Dependencies +------------------ + +The development was done using MSVC 2013 then MS Visual Studio Community 2015, Version 14.0.25431.01 Update 3. The code may or may not compile under linux/gcc (probably not). + +* bost-1_63 +* Ogre 1.10.0 +* ffmpeg-20140823-git-7444cf9-win64-dev +* zlib 1.2.8 +* Qt5 +